<commit_before>import typing
from collections import OrderedDict
from itertools import chain

from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Field
from django.utils.translation import ugettext_lazy as _

from c3nav.editor.utils import is_created_pk
from c3nav.editor.wrappers import ModelInstanceWrapper
from c3nav.mapdata.fields import JSONField

class ChangedObjectManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().select_related('content_type')

class ChangedObject(models.Model):
    changeset = models.ForeignKey('editor.ChangeSet', on_delete=models.CASCADE, verbose_name=_('Change Set'))
    created = models.DateTimeField(auto_now_add=True, verbose_name=_('created'))
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    existing_object_pk = models.PositiveIntegerField(null=True, verbose_name=_('id of existing object'))
    updated_fields = JSONField(default={}, verbose_name=_('updated fields'))
    m2m_added = JSONField(default={}, verbose_name=_('added m2m values'))
    m2m_removed = JSONField(default={}, verbose_name=_('removed m2m values'))
    deleted = models.BooleanField(default=False, verbose_name=_('object was deleted'))

    objects = ChangedObjectManager()

    class Meta:
        verbose_name = _('Changed object')
        verbose_name_plural = _('Changed objects')
        default_related_name = 'changed_objects_set'
        base_manager_name = 'objects'
        unique_together = ('changeset', 'content_type', 'existing_object_pk')
        ordering = ['created', 'pk']

    def __init__(self, *args, **kwargs):
        model_class = kwargs.pop('model_class', None)
        super().__init__(*args, **kwargs)
        self._set_object = None
        self._m2m_added_cache = {name: set(values) for name, values in self.m2m_added.items()}
        self._m2m_removed_cache = {name: set(values) for name, values in self.m2m_removed.items()}
        if model_class is not None:
            self.model_class = model_class

    @property
    def model_class(self) -> typing.Optional[typing.Type[models.Model]]:
        return self.content_type.model_class()

    @model_class.setter
    def model_class(self, value: typing.Optional[typing.Type[models.Model]]):
        self.content_type = ContentType.objects.get_for_model(value)

    @property
    def obj_pk(self) -> typing.Union[int, str]:
        if not self.is_created:
            return self.existing_object_pk
        return 'c'+str(self.pk)

    @property
    def obj(self) -> ModelInstanceWrapper:
        return self.get_obj(get_foreign_objects=True)

    @property
    def is_created(self):
        return self.existing_object_pk is None

    def get_obj(self, get_foreign_objects=False) -> ModelInstanceWrapper:
        model = self.model_class

        if not self.is_created:
            if self._set_object is None:
                self._set_object = self.changeset.wrap_instance(model.objects.get(pk=self.existing_object_pk))

            # noinspection PyTypeChecker
            return self._set_object

        pk = self.obj_pk

        obj = model()
        obj.pk = pk
        if model._meta.pk.is_relation:
            setattr(obj, model._meta.pk.related_model._meta.pk.attname, pk)
        obj._state.adding = False
        return self.changeset.wrap_instance(obj)

    def add_relevant_object_pks(self, object_pks, many=True):
        object_pks.setdefault(self.model_class, set()).add(self.obj_pk)
        for name, value in self.updated_fields.items():
            if name.startswith('title_'):
                continue
            field = self.model_class._meta.get_field(name)
            if field.is_relation:
                object_pks.setdefault(field.related_model, set()).add(value)

        if many:
            for name, value in chain(self._m2m_added_cache.items(), self._m2m_removed_cache.items()):
                field = self.model_class._meta.get_field(name)
                object_pks.setdefault(field.related_model, set()).update(value)

    def update_changeset_cache(self):
        if self.pk is None:
            return

        model = self.model_class
        pk = self.obj_pk

        self.changeset.changed_objects.setdefault(model, {})[pk] = self

        if self.is_created:
            if not self.deleted:
                self.changeset.created_objects.setdefault(model, {})[pk] = self.updated_fields
        else:
            if not self.deleted:
                self.changeset.updated_existing.setdefault(model, {})[pk] = self.updated_fields
                self.changeset.deleted_existing.setdefault(model, set()).discard(pk)
            else:
                self.changeset.updated_existing.setdefault(model, {}).pop(pk, None)
                self.changeset.deleted_existing.setdefault(model, set()).add(pk)

        if not self.deleted:
            self.changeset.m2m_added.setdefault(model, {})[pk] = self._m2m_added_cache
            self.changeset.m2m_removed.setdefault(model, {})[pk] = self._m2m_removed_cache
        else:
            self.changeset.m2m_added.get(model, {}).pop(pk, None)
            self.changeset.m2m_removed.get(model, {}).pop(pk, None)

    def apply_to_instance(self, instance: ModelInstanceWrapper):
        for name, value in self.updated_fields.items():
            if name.startswith('title_'):
                if not value:
                    instance.titles.pop(name[6:], None)
                else:
                    instance.titles[name[6:]] = value
                continue

            field = instance._meta.get_field(name)
            if not field.is_relation:
                setattr(instance, field.name, field.to_python(value))
            elif field.many_to_one or field.one_to_one:
                if is_created_pk(value):
                    try:
                        obj = self.changeset.get_created_object(field.related_model, value, allow_deleted=True)
                    except field.related_model.DoesNotExist:
                        pass
                    else:
                        setattr(instance, field.get_cache_name(), obj)
                else:
                    try:
                        delattr(instance, field.get_cache_name())
                    except AttributeError:
                        pass
                setattr(instance, field.attname, value)
            else:
                raise NotImplementedError

    def clean_updated_fields(self, objects=None):
        if self.is_created:
            current_obj = self.model_class()
        elif objects is not None:
            current_obj = objects[self.model_class][self.existing_object_pk]
        else:
            current_obj = self.model_class.objects.get(pk=self.existing_object_pk)

        delete_fields = set()
        for name, new_value in self.updated_fields.items():
            if name.startswith('title_'):
                current_value = current_obj.titles.get(name[6:], '')
            else:
                field = self.model_class._meta.get_field(name)

                if not field.is_relation:
                    current_value = field.get_prep_value(getattr(current_obj, field.name))
                elif field.many_to_one or field.one_to_one:
                    current_value = getattr(current_obj, field.attname)
                else:
                    raise NotImplementedError

            if current_value == new_value:
                delete_fields.add(name)

        self.updated_fields = {name: value for name, value in self.updated_fields.items() if name not in delete_fields}
        return delete_fields

    def handle_deleted_object_pks(self, deleted_object_pks):
        if self.obj_pk in deleted_object_pks[self.model_class]:
            self.delete()
            return False

        for name, value in self.updated_fields.items():
            if name.startswith('title_'):
                continue
            field = self.model_class._meta.get_field(name)
            if field.is_relation:
                if value in deleted_object_pks[field.related_model]:
                    deleted_object_pks[self.model_class].add(self.obj_pk)
                    self.delete()
                    return False

        changed = False
        for name, value in chain(self._m2m_added_cache.items(), self._m2m_removed_cache.items()):
            field = self.model_class._meta.get_field(name)
            if deleted_object_pks[field.related_model] & value:
                value.difference_update(deleted_object_pks[field.related_model])
                changed = True

        return changed

    def save_instance(self, instance):
        old_updated_fields = self.updated_fields
        self.updated_fields = {}
        for field in self.model_class._meta.get_fields():
            if not isinstance(field, Field) or field.primary_key:
                continue

            if not field.is_relation:
                value = getattr(instance, field.name)
                if field.name == 'titles':
                    for lang, title in value.items():
                        self.updated_fields['title_'+lang] = title
                else:
                    self.updated_fields[field.name] = field.get_prep_value(value)
            elif field.many_to_one or field.one_to_one:
                try:
                    value = getattr(instance, field.get_cache_name())
                except AttributeError:
                    value = getattr(instance, field.attname)
                else:
                    value = None if value is None else value.pk
                self.updated_fields[field.name] = value

        self.clean_updated_fields()
        for name, value in self.updated_fields.items():
            if old_updated_fields.get(name, None) != value:
                self.changeset._object_changed = True
                break
        self.save()
        if instance.pk is None and self.pk is not None:
            instance.pk = self.obj_pk

    def can_delete(self):
        for field in self.model_class._meta.get_fields():
            if not field.one_to_many:
                continue
            related_model = field.related_model
            if related_model._meta.app_label != 'mapdata':
                continue
            kwargs = {field.field.name+'__pk': self.obj_pk}
            if self.changeset.wrap_model(related_model).objects.filter(**kwargs).exists():
                return False
        return True

    def get_missing_dependencies(self, force_query=False, max_one=False):
        result = set()
        if not self.deleted:
            return result
        for field in self.model_class._meta.get_fields():
            if not field.many_to_one:
                continue
            if field.name not in self.updated_fields:
                continue
            related_model = field.related_model
            if related_model._meta.app_label != 'mapdata':
                continue

            pk = self.updated_fields[field.name]

            if force_query:
                # query here to avoid a race condition
                related_content_type = ContentType.objects.get_for_model(related_model)
                qs = self.changeset.changed_objects_set.filter(content_type=related_content_type)
                if is_created_pk(pk):
                    if not qs.filter(pk=int(pk[2:]), deleted=False).exists():
                        result.add(field.name)
                else:
                    if qs.filter(existing_object_pk=pk, deleted=True).exists():
                        result.add(field.name)
            else:
                if is_created_pk(pk):
                    if pk not in self.changeset.created_objects.get(related_model, ()):
                        result.add(field.name)
                else:
                    if pk in self.changeset.deleted_existing.get(related_model, ()):
                        result.add(field.name)

            if result and max_one:
                return result

        return result

    def mark_deleted(self):
        if not self.can_delete():
            return False
        self.changeset._object_changed = True
        self.deleted = True
        self.save()
        return True

    def clean_m2m(self, objects):
        current_obj = objects[self.model_class][self.obj_pk]
        changed = False
        for name in set(self._m2m_added_cache.keys()) | set(self._m2m_removed_cache.keys()):
            changed = changed or self.m2m_set(name, obj=current_obj)
        return changed

    def m2m_set(self, name, set_pks=None, obj=None):
        if obj is not None:
            pks = set(related_obj.pk for related_obj in getattr(obj, name).all())
        elif not self.is_created:
            field = self.model_class._meta.get_field(name)
            rel_name = field.rel.related_name
            pks = set(field.related_model.objects.filter(**{rel_name+'__pk': self.obj_pk}).values_list('pk', flat=True))
        else:
            pks = set()

        m2m_added_before = self._m2m_added_cache.get(name, set())
        m2m_removed_before = self._m2m_removed_cache.get(name, set())

        if set_pks is None:
            self._m2m_added_cache.get(name, set()).difference_update(pks)
            self._m2m_removed_cache.get(name, set()).intersection_update(pks)
        else:
            self._m2m_added_cache[name] = set_pks - pks
            self._m2m_removed_cache[name] = pks - set_pks

        if not self._m2m_added_cache.get(name, set()):
            self._m2m_added_cache.pop(name, None)
        if not self._m2m_removed_cache.get(name, set()):
            self._m2m_removed_cache.pop(name, None)

        if (m2m_added_before != self._m2m_added_cache.get(name, set()) or
                m2m_removed_before != self._m2m_removed_cache.get(name, set())):
            self.changeset._object_changed = True
            self.save()
            return True
        return False

    def m2m_add(self, name, pks: set):
        self._m2m_added_cache.setdefault(name, set()).update(pks)
        self._m2m_removed_cache.setdefault(name, set()).difference_update(pks)
        self.m2m_set(name)

    def m2m_remove(self, name, pks: set):
        self._m2m_removed_cache.setdefault(name, set()).update(pks)
        self._m2m_added_cache.setdefault(name, set()).difference_update(pks)
        self.m2m_set(name)

    def restore(self):
        if self.deleted is False:
            return
        if self.get_missing_dependencies(force_query=True, max_one=True):
            raise PermissionError
        self.deleted = False
        self.save(standalone=True)

    @property
    def does_something(self):
        return (self.updated_fields or self._m2m_added_cache or self._m2m_removed_cache or self.is_created or
                (not self.is_created and self.deleted))

    def save(self, *args, standalone=False, **kwargs):
        self.m2m_added = {name: tuple(values) for name, values in self._m2m_added_cache.items()}
        self.m2m_removed = {name: tuple(values) for name, values in self._m2m_removed_cache.items()}
        if not self.does_something:
            if self.pk:
                self.delete()
        else:
            self.changeset._object_changed = True
            if not standalone and self.changeset.pk is None:
                self.changeset.save()
                self.changeset = self.changeset
        if self.does_something:
            super().save(*args, **kwargs)
        if not standalone and not self.changeset.fill_changes_cache():
            self.update_changeset_cache()

    def delete(self, **kwargs):
        self.changeset._object_changed = True
        super().delete(**kwargs)

    def __repr__(self):
        return '<ChangedObject #%s on ChangeSet #%s>' % (str(self.pk), str(self.changeset_id))

    def serialize(self):
        return OrderedDict((
            ('type', self.model_class.__name__.lower()),
            ('pk', self.obj_pk),
            ('is_created', self.is_created),
            ('deleted', self.deleted),
            ('updated_fields', self.updated_fields),
            ('m2m_added', self.m2m_added),
            ('m2m_removed', self.m2m_removed),
        ))
<commit_msg>fix clean_m2m on created objects<commit_after>import typing
from collections import OrderedDict
from itertools import chain

from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Field
from django.utils.translation import ugettext_lazy as _

from c3nav.editor.utils import is_created_pk
from c3nav.editor.wrappers import ModelInstanceWrapper
from c3nav.mapdata.fields import JSONField

class ChangedObjectManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().select_related('content_type')

class ChangedObject(models.Model):
    changeset = models.ForeignKey('editor.ChangeSet', on_delete=models.CASCADE, verbose_name=_('Change Set'))
    created = models.DateTimeField(auto_now_add=True, verbose_name=_('created'))
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    existing_object_pk = models.PositiveIntegerField(null=True, verbose_name=_('id of existing object'))
    updated_fields = JSONField(default={}, verbose_name=_('updated fields'))
    m2m_added = JSONField(default={}, verbose_name=_('added m2m values'))
    m2m_removed = JSONField(default={}, verbose_name=_('removed m2m values'))
    deleted = models.BooleanField(default=False, verbose_name=_('object was deleted'))

    objects = ChangedObjectManager()

    class Meta:
        verbose_name = _('Changed object')
        verbose_name_plural = _('Changed objects')
        default_related_name = 'changed_objects_set'
        base_manager_name = 'objects'
        unique_together = ('changeset', 'content_type', 'existing_object_pk')
        ordering = ['created', 'pk']

    def __init__(self, *args, **kwargs):
        model_class = kwargs.pop('model_class', None)
        super().__init__(*args, **kwargs)
        self._set_object = None
        self._m2m_added_cache = {name: set(values) for name, values in self.m2m_added.items()}
        self._m2m_removed_cache = {name: set(values) for name, values in self.m2m_removed.items()}
        if model_class is not None:
            self.model_class = model_class

    @property
    def model_class(self) -> typing.Optional[typing.Type[models.Model]]:
        return self.content_type.model_class()

    @model_class.setter
    def model_class(self, value: typing.Optional[typing.Type[models.Model]]):
        self.content_type = ContentType.objects.get_for_model(value)

    @property
    def obj_pk(self) -> typing.Union[int, str]:
        if not self.is_created:
            return self.existing_object_pk
        return 'c'+str(self.pk)

    @property
    def obj(self) -> ModelInstanceWrapper:
        return self.get_obj(get_foreign_objects=True)

    @property
    def is_created(self):
        return self.existing_object_pk is None

    def get_obj(self, get_foreign_objects=False) -> ModelInstanceWrapper:
        model = self.model_class

        if not self.is_created:
            if self._set_object is None:
                self._set_object = self.changeset.wrap_instance(model.objects.get(pk=self.existing_object_pk))

            # noinspection PyTypeChecker
            return self._set_object

        pk = self.obj_pk

        obj = model()
        obj.pk = pk
        if model._meta.pk.is_relation:
            setattr(obj, model._meta.pk.related_model._meta.pk.attname, pk)
        obj._state.adding = False
        return self.changeset.wrap_instance(obj)

    def add_relevant_object_pks(self, object_pks, many=True):
        object_pks.setdefault(self.model_class, set()).add(self.obj_pk)
        for name, value in self.updated_fields.items():
            if name.startswith('title_'):
                continue
            field = self.model_class._meta.get_field(name)
            if field.is_relation:
                object_pks.setdefault(field.related_model, set()).add(value)

        if many:
            for name, value in chain(self._m2m_added_cache.items(), self._m2m_removed_cache.items()):
                field = self.model_class._meta.get_field(name)
                object_pks.setdefault(field.related_model, set()).update(value)

    def update_changeset_cache(self):
        if self.pk is None:
            return

        model = self.model_class
        pk = self.obj_pk

        self.changeset.changed_objects.setdefault(model, {})[pk] = self

        if self.is_created:
            if not self.deleted:
                self.changeset.created_objects.setdefault(model, {})[pk] = self.updated_fields
        else:
            if not self.deleted:
                self.changeset.updated_existing.setdefault(model, {})[pk] = self.updated_fields
                self.changeset.deleted_existing.setdefault(model, set()).discard(pk)
            else:
                self.changeset.updated_existing.setdefault(model, {}).pop(pk, None)
                self.changeset.deleted_existing.setdefault(model, set()).add(pk)

        if not self.deleted:
            self.changeset.m2m_added.setdefault(model, {})[pk] = self._m2m_added_cache
            self.changeset.m2m_removed.setdefault(model, {})[pk] = self._m2m_removed_cache
        else:
            self.changeset.m2m_added.get(model, {}).pop(pk, None)
            self.changeset.m2m_removed.get(model, {}).pop(pk, None)

    def apply_to_instance(self, instance: ModelInstanceWrapper):
        for name, value in self.updated_fields.items():
            if name.startswith('title_'):
                if not value:
                    instance.titles.pop(name[6:], None)
                else:
                    instance.titles[name[6:]] = value
                continue

            field = instance._meta.get_field(name)
            if not field.is_relation:
                setattr(instance, field.name, field.to_python(value))
            elif field.many_to_one or field.one_to_one:
                if is_created_pk(value):
                    try:
                        obj = self.changeset.get_created_object(field.related_model, value, allow_deleted=True)
                    except field.related_model.DoesNotExist:
                        pass
                    else:
                        setattr(instance, field.get_cache_name(), obj)
                else:
                    try:
                        delattr(instance, field.get_cache_name())
                    except AttributeError:
                        pass
                setattr(instance, field.attname, value)
            else:
                raise NotImplementedError

    def clean_updated_fields(self, objects=None):
        if self.is_created:
            current_obj = self.model_class()
        elif objects is not None:
            current_obj = objects[self.model_class][self.existing_object_pk]
        else:
            current_obj = self.model_class.objects.get(pk=self.existing_object_pk)

        delete_fields = set()
        for name, new_value in self.updated_fields.items():
            if name.startswith('title_'):
                current_value = current_obj.titles.get(name[6:], '')
            else:
                field = self.model_class._meta.get_field(name)

                if not field.is_relation:
                    current_value = field.get_prep_value(getattr(current_obj, field.name))
                elif field.many_to_one or field.one_to_one:
                    current_value = getattr(current_obj, field.attname)
                else:
                    raise NotImplementedError

            if current_value == new_value:
                delete_fields.add(name)

        self.updated_fields = {name: value for name, value in self.updated_fields.items() if name not in delete_fields}
        return delete_fields

    def handle_deleted_object_pks(self, deleted_object_pks):
        if self.obj_pk in deleted_object_pks[self.model_class]:
            self.delete()
            return False

        for name, value in self.updated_fields.items():
            if name.startswith('title_'):
                continue
            field = self.model_class._meta.get_field(name)
            if field.is_relation:
                if value in deleted_object_pks[field.related_model]:
                    deleted_object_pks[self.model_class].add(self.obj_pk)
                    self.delete()
                    return False

        changed = False
        for name, value in chain(self._m2m_added_cache.items(), self._m2m_removed_cache.items()):
            field = self.model_class._meta.get_field(name)
            if deleted_object_pks[field.related_model] & value:
                value.difference_update(deleted_object_pks[field.related_model])
                changed = True

        return changed

    def save_instance(self, instance):
        old_updated_fields = self.updated_fields
        self.updated_fields = {}
        for field in self.model_class._meta.get_fields():
            if not isinstance(field, Field) or field.primary_key:
                continue

            if not field.is_relation:
                value = getattr(instance, field.name)
                if field.name == 'titles':
                    for lang, title in value.items():
                        self.updated_fields['title_'+lang] = title
                else:
                    self.updated_fields[field.name] = field.get_prep_value(value)
            elif field.many_to_one or field.one_to_one:
                try:
                    value = getattr(instance, field.get_cache_name())
                except AttributeError:
                    value = getattr(instance, field.attname)
                else:
                    value = None if value is None else value.pk
                self.updated_fields[field.name] = value

        self.clean_updated_fields()
        for name, value in self.updated_fields.items():
            if old_updated_fields.get(name, None) != value:
                self.changeset._object_changed = True
                break
        self.save()
        if instance.pk is None and self.pk is not None:
            instance.pk = self.obj_pk

    def can_delete(self):
        for field in self.model_class._meta.get_fields():
            if not field.one_to_many:
                continue
            related_model = field.related_model
            if related_model._meta.app_label != 'mapdata':
                continue
            kwargs = {field.field.name+'__pk': self.obj_pk}
            if self.changeset.wrap_model(related_model).objects.filter(**kwargs).exists():
                return False
        return True

    def get_missing_dependencies(self, force_query=False, max_one=False):
        result = set()
        if not self.deleted:
            return result
        for field in self.model_class._meta.get_fields():
            if not field.many_to_one:
                continue
            if field.name not in self.updated_fields:
                continue
            related_model = field.related_model
            if related_model._meta.app_label != 'mapdata':
                continue

            pk = self.updated_fields[field.name]

            if force_query:
                # query here to avoid a race condition
                related_content_type = ContentType.objects.get_for_model(related_model)
                qs = self.changeset.changed_objects_set.filter(content_type=related_content_type)
                if is_created_pk(pk):
                    if not qs.filter(pk=int(pk[2:]), deleted=False).exists():
                        result.add(field.name)
                else:
                    if qs.filter(existing_object_pk=pk, deleted=True).exists():
                        result.add(field.name)
            else:
                if is_created_pk(pk):
                    if pk not in self.changeset.created_objects.get(related_model, ()):
                        result.add(field.name)
                else:
                    if pk in self.changeset.deleted_existing.get(related_model, ()):
                        result.add(field.name)

            if result and max_one:
                return result

        return result

    def mark_deleted(self):
        if not self.can_delete():
            return False
        self.changeset._object_changed = True
        self.deleted = True
        self.save()
        return True

    def clean_m2m(self, objects):
        current_obj = objects[self.model_class][self.obj_pk]
        changed = False
        for name in set(self._m2m_added_cache.keys()) | set(self._m2m_removed_cache.keys()):
            changed = changed or self.m2m_set(name, obj=self.changeset.wrap_instance(current_obj))
        return changed

    def m2m_set(self, name, set_pks=None, obj=None):
        if obj is not None:
            pks = set(related_obj.pk for related_obj in getattr(obj, name).all())
        elif not self.is_created:
            field = self.model_class._meta.get_field(name)
            rel_name = field.rel.related_name
            pks = set(field.related_model.objects.filter(**{rel_name+'__pk': self.obj_pk}).values_list('pk', flat=True))
        else:
            pks = set()

        m2m_added_before = self._m2m_added_cache.get(name, set())
        m2m_removed_before = self._m2m_removed_cache.get(name, set())

        if set_pks is None:
            self._m2m_added_cache.get(name, set()).difference_update(pks)
            self._m2m_removed_cache.get(name, set()).intersection_update(pks)
        else:
            self._m2m_added_cache[name] = set_pks - pks
            self._m2m_removed_cache[name] = pks - set_pks

        if not self._m2m_added_cache.get(name, set()):
            self._m2m_added_cache.pop(name, None)
        if not self._m2m_removed_cache.get(name, set()):
            self._m2m_removed_cache.pop(name, None)

        if (m2m_added_before != self._m2m_added_cache.get(name, set()) or
                m2m_removed_before != self._m2m_removed_cache.get(name, set())):
            self.changeset._object_changed = True
            self.save()
            return True
        return False

    def m2m_add(self, name, pks: set):
        self._m2m_added_cache.setdefault(name, set()).update(pks)
        self._m2m_removed_cache.setdefault(name, set()).difference_update(pks)
        self.m2m_set(name)

    def m2m_remove(self, name, pks: set):
        self._m2m_removed_cache.setdefault(name, set()).update(pks)
        self._m2m_added_cache.setdefault(name, set()).difference_update(pks)
        self.m2m_set(name)

    def restore(self):
        if self.deleted is False:
            return
        if self.get_missing_dependencies(force_query=True, max_one=True):
            raise PermissionError
        self.deleted = False
        self.save(standalone=True)

    @property
    def does_something(self):
        return (self.updated_fields or self._m2m_added_cache or self._m2m_removed_cache or self.is_created or
                (not self.is_created and self.deleted))

    def save(self, *args, standalone=False, **kwargs):
        self.m2m_added = {name: tuple(values) for name, values in self._m2m_added_cache.items()}
        self.m2m_removed = {name: tuple(values) for name, values in self._m2m_removed_cache.items()}
        if not self.does_something:
            if self.pk:
                self.delete()
        else:
            self.changeset._object_changed = True
            if not standalone and self.changeset.pk is None:
                self.changeset.save()
                self.changeset = self.changeset
        if self.does_something:
            super().save(*args, **kwargs)
        if not standalone and not self.changeset.fill_changes_cache():
            self.update_changeset_cache()

    def delete(self, **kwargs):
        self.changeset._object_changed = True
        super().delete(**kwargs)

    def __repr__(self):
        return '<ChangedObject #%s on ChangeSet #%s>' % (str(self.pk), str(self.changeset_id))

    def serialize(self):
        return OrderedDict((
            ('type', self.model_class.__name__.lower()),
            ('pk', self.obj_pk),
            ('is_created', self.is_created),
            ('deleted', self.deleted),
            ('updated_fields', self.updated_fields),
            ('m2m_added', self.m2m_added),
            ('m2m_removed', self.m2m_removed),
        ))
<|endoftext|>


<commit_before># Time:  O(n)
# Space: O(n)

class Solution(object):
    def calPoints(self, ops):
        """
        :type ops: List[str]
        :rtype: int
        """
        history = []
        for op in ops:
            if op == '+':
                history.append(history[-1] + history[-2])
            elif op == 'D':
                history.append(history[-1] * 2)
            elif op == 'C':
                history.pop()
            else:
                history.append(int(op))
        return sum(history)
<commit_msg>Update baseball-game.py<commit_after># Time:  O(n)
# Space: O(n)

# You're now a baseball game point recorder.
# Given a list of strings, each string can be one of the 4 following types:
#
# 1. Integer (one round's score): Directly represents the number of points you get in this round.
# 2. "+" (one round's score): Represents that the points you get in this round are
#                             the sum of the last two valid round's points.
# 3. "D" (one round's score): Represents that the points you get in this round are
#                             the doubled data of the last valid round's points.
# 4. "C" (an operation, which isn't a round's score): Represents the last valid round's points
#                              you get were invalid and should be removed.
#
# Each round's operation is permanent and could have an impact on the round before and the round after.
# You need to return the sum of the points you could get in all the rounds.
#
# Example 1:
#
# Input: ["5","2","C","D","+"]
# Output: 30
# Explanation:
# Round 1: You could get 5 points. The sum is: 5.
# Round 2: You could get 2 points. The sum is: 7.
# Operation 1: The round 2's data was invalid. The sum is: 5.
# Round 3: You could get 10 points (the round 2's data has been removed). The sum is: 15.
# Round 4: You could get 5 + 10 = 15 points. The sum is: 30.
#
# Example 2:
#
# Input: ["5","-2","4","C","D","9","+","+"]
# Output: 27
# Explanation:
# Round 1: You could get 5 points. The sum is: 5.
# Round 2: You could get -2 points. The sum is: 3.
# Round 3: You could get 4 points. The sum is: 7.
# Operation 1: The round 3's data is invalid. The sum is: 3.
# Round 4: You could get -4 points (the round 3's data has been removed). The sum is: -1.
# Round 5: You could get 9 points. The sum is: 8.
# Round 6: You could get -4 + 9 = 5 points. The sum is 13.
# Round 7: You could get 9 + 5 = 14 points. The sum is 27.
#
# Note:
# The size of the input list will be between 1 and 1000.
# Every integer represented in the list will be between -30000 and 30000.

class Solution(object):
    def calPoints(self, ops):
        """
        :type ops: List[str]
        :rtype: int
        """
        history = []
        for op in ops:
            if op == '+':
                history.append(history[-1] + history[-2])
            elif op == 'D':
                history.append(history[-1] * 2)
            elif op == 'C':
                history.pop()
            else:
                history.append(int(op))
        return sum(history)
<|endoftext|>


<commit_before># Copyright 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Finds browsers that can be controlled by telemetry."""

import logging
import operator

from telemetry import decorators
from telemetry.internal.backends.chrome import android_browser_finder
from telemetry.internal.backends.chrome import cros_browser_finder
from telemetry.internal.backends.chrome import desktop_browser_finder
from telemetry.internal.backends.chrome import ios_browser_finder
from telemetry.internal.browser import browser_finder_exceptions
from telemetry.internal.platform import device_finder

BROWSER_FINDERS = [
  desktop_browser_finder,
  android_browser_finder,
  cros_browser_finder,
  ios_browser_finder,
  ]

def FindAllBrowserTypes(options):
  return reduce(operator.add,
                [bf.FindAllBrowserTypes(options) for bf in BROWSER_FINDERS])

@decorators.Cache
def FindBrowser(options):
  """Finds the best PossibleBrowser object given a BrowserOptions object.

  Args:
    A BrowserOptions object.

  Returns:
    A PossibleBrowser object.

  Raises:
    BrowserFinderException: Options improperly set, or an error occurred.
  """
  if options.__class__.__name__ == '_FakeBrowserFinderOptions':
    return options.fake_possible_browser
  if options.browser_type == 'exact' and options.browser_executable == None:
    raise browser_finder_exceptions.BrowserFinderException(
        '--browser=exact requires --browser-executable to be set.')
  if options.browser_type != 'exact' and options.browser_executable != None:
    raise browser_finder_exceptions.BrowserFinderException(
        '--browser-executable requires --browser=exact.')

  if options.browser_type == 'cros-chrome' and options.cros_remote == None:
    raise browser_finder_exceptions.BrowserFinderException(
        'browser_type=cros-chrome requires cros_remote be set.')
  if (options.browser_type != 'cros-chrome' and
      options.browser_type != 'cros-chrome-guest' and
      options.cros_remote != None):
    raise browser_finder_exceptions.BrowserFinderException(
        '--remote requires --browser=cros-chrome or cros-chrome-guest.')

  devices = device_finder.GetDevicesMatchingOptions(options)
  browsers = []
  default_browsers = []
  for device in devices:
    for finder in BROWSER_FINDERS:
      if(options.browser_type and options.browser_type != 'any' and
         options.browser_type not in finder.FindAllBrowserTypes(options)):
        continue
      curr_browsers = finder.FindAllAvailableBrowsers(options, device)
      new_default_browser = finder.SelectDefaultBrowser(curr_browsers)
      if new_default_browser:
        default_browsers.append(new_default_browser)
      browsers.extend(curr_browsers)

  if options.browser_type == None:
    if default_browsers:
      default_browser = sorted(default_browsers,
                               key=lambda b: b.last_modification_time())[-1]

      logging.warning('--browser omitted. Using most recent local build: %s' %
                      default_browser.browser_type)
      default_browser.UpdateExecutableIfNeeded()
      return default_browser

    if len(browsers) == 1:
      logging.warning('--browser omitted. Using only available browser: %s' %
                      browsers[0].browser_type)
      browsers[0].UpdateExecutableIfNeeded()
      return browsers[0]

    raise browser_finder_exceptions.BrowserTypeRequiredException(
        '--browser must be specified. Available browsers:\n%s' %
        '\n'.join(sorted(set([b.browser_type for b in browsers]))))

  matching_browsers = [b for b in browsers
      if b.browser_type == options.browser_type and
      b.SupportsOptions(options.browser_options)]

  chosen_browser = None
  if len(matching_browsers) == 1:
    chosen_browser = matching_browsers[0]
  elif len(matching_browsers) > 1:
    logging.warning('Multiple browsers of the same type found: %s' % (
                    repr(matching_browsers)))
    chosen_browser = sorted(matching_browsers,
                            key=lambda b: b.last_modification_time())[-1]

  if chosen_browser:
    logging.info('Chose browser: %s' % (repr(chosen_browser)))
    chosen_browser.UpdateExecutableIfNeeded()

  return chosen_browser

@decorators.Cache
def GetAllAvailableBrowsers(options, device):
  """Returns a list of available browsers on the device.

  Args:
    options: A BrowserOptions object.
    device: The target device, which can be None.

  Returns:
    A list of browser instances.

  Raises:
    BrowserFinderException: Options are improperly set, or an error occurred.
  """
  if not device:
    return []
  possible_browsers = []
  for browser_finder in BROWSER_FINDERS:
    possible_browsers.extend(
      browser_finder.FindAllAvailableBrowsers(options, device))
  return possible_browsers

@decorators.Cache
def GetAllAvailableBrowserTypes(options):
  """Returns a list of available browser types.

  Args:
    options: A BrowserOptions object.

  Returns:
    A list of browser type strings.

  Raises:
    BrowserFinderException: Options are improperly set, or an error occurred.
  """
  devices = device_finder.GetDevicesMatchingOptions(options)
  possible_browsers = []
  for device in devices:
    possible_browsers.extend(GetAllAvailableBrowsers(options, device))
  type_list = set([browser.browser_type for browser in possible_browsers])
  # The reference build should be available for mac, linux and win, but the
  # desktop browser finder won't return it in the list of browsers.
  for browser in possible_browsers:
    if (browser.target_os == 'darwin' or browser.target_os.startswith('linux')
        or browser.target_os.startswith('win')):
      type_list.add('reference')
      break
  type_list = list(type_list)
  type_list.sort()
  return type_list
<commit_msg>Revert of [Telemetry] Remove code that handle --browser=any (patchset #1 id:1 of https://codereview.chromium.org/2750463004/ )<commit_after># Copyright 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Finds browsers that can be controlled by telemetry."""

import logging
import operator

from telemetry import decorators
from telemetry.internal.backends.chrome import android_browser_finder
from telemetry.internal.backends.chrome import cros_browser_finder
from telemetry.internal.backends.chrome import desktop_browser_finder
from telemetry.internal.backends.chrome import ios_browser_finder
from telemetry.internal.browser import browser_finder_exceptions
from telemetry.internal.platform import device_finder

BROWSER_FINDERS = [
  desktop_browser_finder,
  android_browser_finder,
  cros_browser_finder,
  ios_browser_finder,
  ]

def FindAllBrowserTypes(options):
  return reduce(operator.add,
                [bf.FindAllBrowserTypes(options) for bf in BROWSER_FINDERS])

@decorators.Cache
def FindBrowser(options):
  """Finds the best PossibleBrowser object given a BrowserOptions object.

  Args:
    A BrowserOptions object.

  Returns:
    A PossibleBrowser object.

  Raises:
    BrowserFinderException: Options improperly set, or an error occurred.
  """
  if options.__class__.__name__ == '_FakeBrowserFinderOptions':
    return options.fake_possible_browser
  if options.browser_type == 'exact' and options.browser_executable == None:
    raise browser_finder_exceptions.BrowserFinderException(
        '--browser=exact requires --browser-executable to be set.')
  if options.browser_type != 'exact' and options.browser_executable != None:
    raise browser_finder_exceptions.BrowserFinderException(
        '--browser-executable requires --browser=exact.')

  if options.browser_type == 'cros-chrome' and options.cros_remote == None:
    raise browser_finder_exceptions.BrowserFinderException(
        'browser_type=cros-chrome requires cros_remote be set.')
  if (options.browser_type != 'cros-chrome' and
      options.browser_type != 'cros-chrome-guest' and
      options.cros_remote != None):
    raise browser_finder_exceptions.BrowserFinderException(
        '--remote requires --browser=cros-chrome or cros-chrome-guest.')

  devices = device_finder.GetDevicesMatchingOptions(options)
  browsers = []
  default_browsers = []
  for device in devices:
    for finder in BROWSER_FINDERS:
      if(options.browser_type and options.browser_type != 'any' and
         options.browser_type not in finder.FindAllBrowserTypes(options)):
        continue
      curr_browsers = finder.FindAllAvailableBrowsers(options, device)
      new_default_browser = finder.SelectDefaultBrowser(curr_browsers)
      if new_default_browser:
        default_browsers.append(new_default_browser)
      browsers.extend(curr_browsers)

  if options.browser_type == None:
    if default_browsers:
      default_browser = sorted(default_browsers,
                               key=lambda b: b.last_modification_time())[-1]

      logging.warning('--browser omitted. Using most recent local build: %s' %
                      default_browser.browser_type)
      default_browser.UpdateExecutableIfNeeded()
      return default_browser

    if len(browsers) == 1:
      logging.warning('--browser omitted. Using only available browser: %s' %
                      browsers[0].browser_type)
      browsers[0].UpdateExecutableIfNeeded()
      return browsers[0]

    raise browser_finder_exceptions.BrowserTypeRequiredException(
        '--browser must be specified. Available browsers:\n%s' %
        '\n'.join(sorted(set([b.browser_type for b in browsers]))))

  if options.browser_type == 'any':
    types = FindAllBrowserTypes(options)
    def CompareBrowsersOnTypePriority(x, y):
      x_idx = types.index(x.browser_type)
      y_idx = types.index(y.browser_type)
      return x_idx - y_idx
    browsers.sort(CompareBrowsersOnTypePriority)
    if len(browsers) >= 1:
      browsers[0].UpdateExecutableIfNeeded()
      return browsers[0]
    else:
      return None

  matching_browsers = [b for b in browsers
      if b.browser_type == options.browser_type and
      b.SupportsOptions(options.browser_options)]

  chosen_browser = None
  if len(matching_browsers) == 1:
    chosen_browser = matching_browsers[0]
  elif len(matching_browsers) > 1:
    logging.warning('Multiple browsers of the same type found: %s' % (
                    repr(matching_browsers)))
    chosen_browser = sorted(matching_browsers,
                            key=lambda b: b.last_modification_time())[-1]

  if chosen_browser:
    logging.info('Chose browser: %s' % (repr(chosen_browser)))
    chosen_browser.UpdateExecutableIfNeeded()

  return chosen_browser

@decorators.Cache
def GetAllAvailableBrowsers(options, device):
  """Returns a list of available browsers on the device.

  Args:
    options: A BrowserOptions object.
    device: The target device, which can be None.

  Returns:
    A list of browser instances.

  Raises:
    BrowserFinderException: Options are improperly set, or an error occurred.
  """
  if not device:
    return []
  possible_browsers = []
  for browser_finder in BROWSER_FINDERS:
    possible_browsers.extend(
      browser_finder.FindAllAvailableBrowsers(options, device))
  return possible_browsers

@decorators.Cache
def GetAllAvailableBrowserTypes(options):
  """Returns a list of available browser types.

  Args:
    options: A BrowserOptions object.

  Returns:
    A list of browser type strings.

  Raises:
    BrowserFinderException: Options are improperly set, or an error occurred.
  """
  devices = device_finder.GetDevicesMatchingOptions(options)
  possible_browsers = []
  for device in devices:
    possible_browsers.extend(GetAllAvailableBrowsers(options, device))
  type_list = set([browser.browser_type for browser in possible_browsers])
  # The reference build should be available for mac, linux and win, but the
  # desktop browser finder won't return it in the list of browsers.
  for browser in possible_browsers:
    if (browser.target_os == 'darwin' or browser.target_os.startswith('linux')
        or browser.target_os.startswith('win')):
      type_list.add('reference')
      break
  type_list = list(type_list)
  type_list.sort()
  return type_list
<|endoftext|>


<commit_before># -*- coding:utf-8 -*-

import json
from markdown2 import markdown

from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import Max
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.views.generic import DetailView, TemplateView, CreateView, View
from django.views.generic.edit import UpdateView
from django.db.models import Count

from premises.constants import NEWS_CONTENT_COUNT, UPDATED_CONTENT_COUNT
from premises.models import Contention, Premise, SITUATION, OBJECTION, SUPPORT, Report
from premises.forms import ArgumentCreationForm, PremiseCreationForm, PremiseEditForm, ReportForm
from premises.signals import added_premise_for_premise, added_premise_for_contention, reported_as_fallacy
from profiles.models import Profile

class ContentionDetailView(DetailView):
    template_name = "premises/contention_detail.html"
    model = Contention

    def get_context_data(self, **kwargs):
        contention = self.get_object()
        view = ("list-view" if self.request.GET.get("view") == "list"
                            else "tree-view")
        edit_mode = (
                self.request.user.is_superuser or
                self.request.user.is_staff or
                contention.user == self.request.user)
        return super(ContentionDetailView, self).get_context_data(
            view=view,
            path=contention.get_absolute_url(),
            edit_mode=edit_mode,
            **kwargs)

class ContentionJsonView(DetailView):
    model = Contention

    def render_to_response(self, context, **response_kwargs):
        contention = self.get_object(self.get_queryset())
        return HttpResponse(json.dumps({
            "nodes": self.build_tree(contention, self.request.user),
        }), content_type="application/json")

    def build_tree(self, contention, user):
        return {
            "name": contention.title,
            "parent": None,
            "pk": contention.pk,
            "owner": contention.owner,
            "sources": contention.sources,
            "is_singular": self.is_singular(contention),
            "children": self.get_premises(contention, user)
        }

    def get_premises(self, contention, user, parent=None):
        children = [{
            "pk": premise.pk,
            "name": premise.text,
            "parent": parent.text if parent else None,
            "reportable_by_authenticated_user": self.user_can_report(premise, user),
            "report_count": premise.reports.count(),
            "user": {
                "id": premise.user.id,
                "username": premise.user.username,
                "absolute_url": reverse("auth_profile",
                                        args=[premise.user.username])
            },
            "sources": premise.sources,
            "premise_type": premise.premise_class(),
            "children": (self.get_premises(contention, user, parent=premise)
                         if premise.published_children().exists() else [])
        } for premise in contention.published_premises(parent)]
        return children

    def user_can_report(self, premise, user):
        if user.is_authenticated() and user != premise.user:
            return not premise.reported_by(user)

        return False

    def is_singular(self, contention):
        result = (contention
                   .premises
                   .all()
                   .aggregate(max_sibling=Max('sibling_count')))
        return result['max_sibling'] <= 1

class HomeView(TemplateView):
    template_name = "index.html"
    tab_class = "featured"

    def get_context_data(self, **kwargs):
        contentions = self.get_contentions()
        if self.request.user.is_authenticated():
            notifications_qs = self.get_unread_notifications()
            notifications = list(notifications_qs)
            self.mark_as_read(notifications_qs)
        else:
            notifications = None
        return super(HomeView, self).get_context_data(
            tab_class=self.tab_class,
            notifications=notifications,
            contentions=contentions, **kwargs)

    def get_unread_notifications(self):
        return (self.request.user
                    .notifications
                    .filter(is_read=False)
                    [:5])

    def mark_as_read(self, notifications):
        pks = notifications.values_list("id", flat=True)
        (self.request.user
             .notifications
             .filter(id__in=pks)
             .update(is_read=True))

    def get_contentions(self):
        return Contention.objects.featured()

class NotificationsView(HomeView):
    template_name = "notifications.html"

    def get_context_data(self, **kwargs):
        notifications_qs = self.request.user.notifications.all()[:40]
        notifications = list(notifications_qs)
        self.mark_as_read(notifications_qs)
        return super(HomeView, self).get_context_data(
            notifications=notifications,
            **kwargs)

class SearchView(HomeView):
    tab_class = 'search'

    def get_context_data(self, **kwargs):
        return super(SearchView, self).get_context_data(
            keywords=self.request.GET.get('keywords') or "",
            **kwargs
        )

    def get_contentions(self):
        keywords = self.request.GET.get('keywords')
        if not keywords or len(keywords) < 2:
            result = []
        else:
            result = Contention.objects.filter(title__icontains=keywords)
        return result

class NewsView(HomeView):
    tab_class = "news"

    def get_contentions(self):
        return Contention.objects.filter(
            is_published=True)[:NEWS_CONTENT_COUNT]

class UpdatedArgumentsView(HomeView):
    tab_class = "updated"

    def get_contentions(self):
        return (Contention
                .objects
                .filter(is_published=True)
                .order_by('-date_modification')
                [:UPDATED_CONTENT_COUNT])

class ControversialArgumentsView(HomeView):
    tab_class = "controversial"

    def get_contentions(self):
        return (Contention
                .objects
                .annotate(num_children=Count('premises'))
                .order_by('-num_children')
                [:UPDATED_CONTENT_COUNT])

class AboutView(TemplateView):
    template_name = "about.html"

    def get_context_data(self, **kwargs):
        content = markdown(render_to_string("about.md"))
        return super(AboutView, self).get_context_data(
            content=content, **kwargs)

class TosView(TemplateView):
    template_name = "tos.html"

    def get_context_data(self, **kwargs):
        content = markdown(render_to_string("tos.md"))
        return super(TosView, self).get_context_data(
            content=content, **kwargs)

class ArgumentCreationView(CreateView):
    template_name = "premises/new_contention.html"
    form_class = ArgumentCreationForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        response = super(ArgumentCreationView, self).form_valid(form)
        form.instance.update_sibling_counts()
        return response

class ArgumentUpdateView(UpdateView):
    template_name = "premises/edit_contention.html"
    form_class = ArgumentCreationForm

    def get_queryset(self):
        contentions = Contention.objects.all()
        if self.request.user.is_superuser:
            return contentions
        return contentions.filter(user=self.request.user)

    def form_valid(self, form):
        form.instance.user = self.request.user
        response = super(ArgumentUpdateView, self).form_valid(form)
        form.instance.update_sibling_counts()
        return response

class ArgumentPublishView(DetailView):

    def get_queryset(self):
        return Contention.objects.filter(user=self.request.user)

    def post(self, request, slug):
        contention = self.get_object()
        contention.is_published = True
        contention.save()
        messages.info(request, u"Argüman yayına alındı.")
        return redirect(contention)

class ArgumentUnpublishView(DetailView):

    def get_queryset(self):
        return Contention.objects.filter(user=self.request.user)

    def post(self, request, slug):
        contention = self.get_object()
        contention.is_published = False
        contention.save()
        messages.info(request, u"Argüman yayından kaldırıldı.")
        return redirect(contention)

class ArgumentDeleteView(DetailView):

    def get_queryset(self):
        return Contention.objects.filter(user=self.request.user)

    def post(self, request, slug):
        contention = self.get_object()
        contention.delete()
        messages.info(request, u"Argümanınız silindi.")
        return redirect("home")

    delete = post

class PremiseEditView(UpdateView):
    template_name = "premises/edit_premise.html"
    form_class = PremiseEditForm

    def get_queryset(self):
        premises = Premise.objects.all()
        if self.request.user.is_superuser:
            return premises
        return premises.filter(user=self.request.user)

    def form_valid(self, form):
        response = super(PremiseEditView, self).form_valid(form)
        form.instance.argument.update_sibling_counts()
        return response

    def get_context_data(self, **kwargs):
        return super(PremiseEditView, self).get_context_data(
            #contention=self.get_contention(),
            **kwargs)

class PremiseCreationView(CreateView):
    template_name = "premises/new_premise.html"
    form_class = PremiseCreationForm

    def get_context_data(self, **kwargs):
        return super(PremiseCreationView, self).get_context_data(
            contention=self.get_contention(),
            parent=self.get_parent(),
            **kwargs)

    def form_valid(self, form):
        contention = self.get_contention()
        form.instance.user = self.request.user
        form.instance.argument = contention
        form.instance.parent = self.get_parent()
        form.instance.is_approved = True
        form.save()
        contention.update_sibling_counts()

        if form.instance.parent:
            added_premise_for_premise.send(sender=self,
                                           premise=form.instance)
        else:
            added_premise_for_contention.send(sender=self,
                                              premise=form.instance)

        return redirect(contention)

    def get_contention(self):
        return get_object_or_404(Contention, slug=self.kwargs['slug'])

    def get_parent(self):
        parent_pk = self.kwargs.get("pk")
        if parent_pk:
            return get_object_or_404(Premise, pk=parent_pk)

class PremiseDeleteView(View):
    def get_premise(self):
        if self.request.user.is_staff:
            premises = Premise.objects.all()
        else:
            premises = Premise.objects.filter(user=self.request.user)
        return get_object_or_404(premises,
                                 pk=self.kwargs['pk'])

    def delete(self, request, *args, **kwargs):
        contention = self.get_premise()
        contention.delete()
        contention.update_sibling_counts()
        return redirect(self.get_contention())

    post = delete

    def get_contention(self):
        return get_object_or_404(Contention, slug=self.kwargs['slug'])

class ReportView(CreateView):
    form_class = ReportForm
    template_name = "premises/report.html"

    def get_context_data(self, **kwargs):
        return super(ReportView, self).get_context_data(
            premise=self.get_premise(),
            **kwargs)

    def get_contention(self):
        return get_object_or_404(Contention, slug=self.kwargs['slug'])

    def get_premise(self):
        return get_object_or_404(Premise, pk=self.kwargs['pk'])

    def form_valid(self, form):
        contention = self.get_contention()
        premise = self.get_premise()
        form.instance.contention = contention
        form.instance.premise = premise
        form.instance.reporter = self.request.user
        form.save()
        reported_as_fallacy.send(sender=self, report=form.instance)
        return redirect(contention)
<commit_msg>Update controversial content<commit_after># -*- coding:utf-8 -*-

import json
from markdown2 import markdown
from datetime import date, timedelta

from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import Max
from django.utils.timezone import now
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.views.generic import DetailView, TemplateView, CreateView, View
from django.views.generic.edit import UpdateView
from django.db.models import Count

from premises.constants import NEWS_CONTENT_COUNT, UPDATED_CONTENT_COUNT
from premises.models import Contention, Premise, SITUATION, OBJECTION, SUPPORT, Report
from premises.forms import ArgumentCreationForm, PremiseCreationForm, PremiseEditForm, ReportForm
from premises.signals import added_premise_for_premise, added_premise_for_contention, reported_as_fallacy
from profiles.models import Profile

class ContentionDetailView(DetailView):
    template_name = "premises/contention_detail.html"
    model = Contention

    def get_context_data(self, **kwargs):
        contention = self.get_object()
        view = ("list-view" if self.request.GET.get("view") == "list"
                            else "tree-view")
        edit_mode = (
                self.request.user.is_superuser or
                self.request.user.is_staff or
                contention.user == self.request.user)
        return super(ContentionDetailView, self).get_context_data(
            view=view,
            path=contention.get_absolute_url(),
            edit_mode=edit_mode,
            **kwargs)

class ContentionJsonView(DetailView):
    model = Contention

    def render_to_response(self, context, **response_kwargs):
        contention = self.get_object(self.get_queryset())
        return HttpResponse(json.dumps({
            "nodes": self.build_tree(contention, self.request.user),
        }), content_type="application/json")

    def build_tree(self, contention, user):
        return {
            "name": contention.title,
            "parent": None,
            "pk": contention.pk,
            "owner": contention.owner,
            "sources": contention.sources,
            "is_singular": self.is_singular(contention),
            "children": self.get_premises(contention, user)
        }

    def get_premises(self, contention, user, parent=None):
        children = [{
            "pk": premise.pk,
            "name": premise.text,
            "parent": parent.text if parent else None,
            "reportable_by_authenticated_user": self.user_can_report(premise, user),
            "report_count": premise.reports.count(),
            "user": {
                "id": premise.user.id,
                "username": premise.user.username,
                "absolute_url": reverse("auth_profile",
                                        args=[premise.user.username])
            },
            "sources": premise.sources,
            "premise_type": premise.premise_class(),
            "children": (self.get_premises(contention, user, parent=premise)
                         if premise.published_children().exists() else [])
        } for premise in contention.published_premises(parent)]
        return children

    def user_can_report(self, premise, user):
        if user.is_authenticated() and user != premise.user:
            return not premise.reported_by(user)

        return False

    def is_singular(self, contention):
        result = (contention
                   .premises
                   .all()
                   .aggregate(max_sibling=Max('sibling_count')))
        return result['max_sibling'] <= 1

class HomeView(TemplateView):
    template_name = "index.html"
    tab_class = "featured"

    def get_context_data(self, **kwargs):
        contentions = self.get_contentions()
        if self.request.user.is_authenticated():
            notifications_qs = self.get_unread_notifications()
            notifications = list(notifications_qs)
            self.mark_as_read(notifications_qs)
        else:
            notifications = None
        return super(HomeView, self).get_context_data(
            tab_class=self.tab_class,
            notifications=notifications,
            contentions=contentions, **kwargs)

    def get_unread_notifications(self):
        return (self.request.user
                    .notifications
                    .filter(is_read=False)
                    [:5])

    def mark_as_read(self, notifications):
        pks = notifications.values_list("id", flat=True)
        (self.request.user
             .notifications
             .filter(id__in=pks)
             .update(is_read=True))

    def get_contentions(self):
        return Contention.objects.featured()

class NotificationsView(HomeView):
    template_name = "notifications.html"

    def get_context_data(self, **kwargs):
        notifications_qs = self.request.user.notifications.all()[:40]
        notifications = list(notifications_qs)
        self.mark_as_read(notifications_qs)
        return super(HomeView, self).get_context_data(
            notifications=notifications,
            **kwargs)

class SearchView(HomeView):
    tab_class = 'search'

    def get_context_data(self, **kwargs):
        return super(SearchView, self).get_context_data(
            keywords=self.request.GET.get('keywords') or "",
            **kwargs
        )

    def get_contentions(self):
        keywords = self.request.GET.get('keywords')
        if not keywords or len(keywords) < 2:
            result = []
        else:
            result = Contention.objects.filter(title__icontains=keywords)
        return result

class NewsView(HomeView):
    tab_class = "news"

    def get_contentions(self):
        return Contention.objects.filter(
            is_published=True)[:NEWS_CONTENT_COUNT]

class UpdatedArgumentsView(HomeView):
    tab_class = "updated"

    def get_contentions(self):
        return (Contention
                .objects
                .filter(is_published=True)
                .order_by('-date_modification')
                [:UPDATED_CONTENT_COUNT])

class ControversialArgumentsView(HomeView):
    tab_class = "controversial"

    def get_contentions(self):
        last_week = now() - timedelta(days=3)
        return (Contention
                .objects
                .annotate(num_children=Count('premises'))
                .order_by('-num_children')
                .filter(date_modification__gte=last_week)
                [:UPDATED_CONTENT_COUNT])

class AboutView(TemplateView):
    template_name = "about.html"

    def get_context_data(self, **kwargs):
        content = markdown(render_to_string("about.md"))
        return super(AboutView, self).get_context_data(
            content=content, **kwargs)

class TosView(TemplateView):
    template_name = "tos.html"

    def get_context_data(self, **kwargs):
        content = markdown(render_to_string("tos.md"))
        return super(TosView, self).get_context_data(
            content=content, **kwargs)

class ArgumentCreationView(CreateView):
    template_name = "premises/new_contention.html"
    form_class = ArgumentCreationForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        response = super(ArgumentCreationView, self).form_valid(form)
        form.instance.update_sibling_counts()
        return response

class ArgumentUpdateView(UpdateView):
    template_name = "premises/edit_contention.html"
    form_class = ArgumentCreationForm

    def get_queryset(self):
        contentions = Contention.objects.all()
        if self.request.user.is_superuser:
            return contentions
        return contentions.filter(user=self.request.user)

    def form_valid(self, form):
        form.instance.user = self.request.user
        response = super(ArgumentUpdateView, self).form_valid(form)
        form.instance.update_sibling_counts()
        return response

class ArgumentPublishView(DetailView):

    def get_queryset(self):
        return Contention.objects.filter(user=self.request.user)

    def post(self, request, slug):
        contention = self.get_object()
        contention.is_published = True
        contention.save()
        messages.info(request, u"Argüman yayına alındı.")
        return redirect(contention)

class ArgumentUnpublishView(DetailView):

    def get_queryset(self):
        return Contention.objects.filter(user=self.request.user)

    def post(self, request, slug):
        contention = self.get_object()
        contention.is_published = False
        contention.save()
        messages.info(request, u"Argüman yayından kaldırıldı.")
        return redirect(contention)

class ArgumentDeleteView(DetailView):

    def get_queryset(self):
        return Contention.objects.filter(user=self.request.user)

    def post(self, request, slug):
        contention = self.get_object()
        contention.delete()
        messages.info(request, u"Argümanınız silindi.")
        return redirect("home")

    delete = post

class PremiseEditView(UpdateView):
    template_name = "premises/edit_premise.html"
    form_class = PremiseEditForm

    def get_queryset(self):
        premises = Premise.objects.all()
        if self.request.user.is_superuser:
            return premises
        return premises.filter(user=self.request.user)

    def form_valid(self, form):
        response = super(PremiseEditView, self).form_valid(form)
        form.instance.argument.update_sibling_counts()
        return response

    def get_context_data(self, **kwargs):
        return super(PremiseEditView, self).get_context_data(
            #contention=self.get_contention(),
            **kwargs)

class PremiseCreationView(CreateView):
    template_name = "premises/new_premise.html"
    form_class = PremiseCreationForm

    def get_context_data(self, **kwargs):
        return super(PremiseCreationView, self).get_context_data(
            contention=self.get_contention(),
            parent=self.get_parent(),
            **kwargs)

    def form_valid(self, form):
        contention = self.get_contention()
        form.instance.user = self.request.user
        form.instance.argument = contention
        form.instance.parent = self.get_parent()
        form.instance.is_approved = True
        form.save()
        contention.update_sibling_counts()

        if form.instance.parent:
            added_premise_for_premise.send(sender=self,
                                           premise=form.instance)
        else:
            added_premise_for_contention.send(sender=self,
                                              premise=form.instance)

        return redirect(contention)

    def get_contention(self):
        return get_object_or_404(Contention, slug=self.kwargs['slug'])

    def get_parent(self):
        parent_pk = self.kwargs.get("pk")
        if parent_pk:
            return get_object_or_404(Premise, pk=parent_pk)

class PremiseDeleteView(View):
    def get_premise(self):
        if self.request.user.is_staff:
            premises = Premise.objects.all()
        else:
            premises = Premise.objects.filter(user=self.request.user)
        return get_object_or_404(premises,
                                 pk=self.kwargs['pk'])

    def delete(self, request, *args, **kwargs):
        contention = self.get_premise()
        contention.delete()
        contention.update_sibling_counts()
        return redirect(self.get_contention())

    post = delete

    def get_contention(self):
        return get_object_or_404(Contention, slug=self.kwargs['slug'])

class ReportView(CreateView):
    form_class = ReportForm
    template_name = "premises/report.html"

    def get_context_data(self, **kwargs):
        return super(ReportView, self).get_context_data(
            premise=self.get_premise(),
            **kwargs)

    def get_contention(self):
        return get_object_or_404(Contention, slug=self.kwargs['slug'])

    def get_premise(self):
        return get_object_or_404(Premise, pk=self.kwargs['pk'])

    def form_valid(self, form):
        contention = self.get_contention()
        premise = self.get_premise()
        form.instance.contention = contention
        form.instance.premise = premise
        form.instance.reporter = self.request.user
        form.save()
        reported_as_fallacy.send(sender=self, report=form.instance)
        return redirect(contention)
<|endoftext|>


<commit_before>#!/bin/env python

from __future__ import print_function

import argparse
import os
import pandas as pd
import message_type as mt
import boto3
from time import sleep
import time
import sys

def GetMessagesInQueue(sqs_resource, queue_name):
    # Get the queue. This returns an SQS.Queue instance
    queue = sqs_resource.get_queue_by_name(QueueName=queue_name)

    # You can now access identifiers and attributes
    num_messages = queue.attributes.get('ApproximateNumberOfMessages')
    return int(num_messages)

def GetNumberOfOutputs(s3_bucket, key):
    return sum(1 for _ in s3_bucket.objects.filter(Prefix=key))

def DeleteMessagesInBucket(s3_bucket, key):
    for key in s3_bucket.list(prefix=key):
        key.delete()

def DownloadMessagesInBucket(s3_bucket, key):
    s3 = boto3.resource('s3')
    for obj in s3_bucket.objects.filter(Prefix=key):
        s3.Object(s3_bucket.name, obj.key).download_file(obj.key)

def DeleteMessagesInBucket(s3_bucket, key):
    s3 = boto3.resource('s3')
    for obj in s3_bucket.objects.filter(Prefix=key):
        s3.Object(s3_bucket.name, obj.key).delete()

def InitializeOutputQueue(queue_name):
    sqs = boto3.resource('sqs')
    output_queue = sqs.create_queue(QueueName=queue_name)

    try:
        output_queue.purge()
    except:
        print("Already purged...")
    return output_queue

def PopulateMessageList(expected_files, message_list, current_message):
    # Just need to assume that first value is the filename
    message_filename = current_message.split(',')[0]
    for idx, filename in enumerate(expected_files):
        if filename == message_filename:
            message_list.append(current_message)
            del expected_files[idx]
            return (expected_files, message_list, True)
    return (expected_files, message_list, False)

def ReceiveNMessagesFromOutputQueue(output_queue, expected_files):
    """
    Function that polls the queue for messages.
    expected_files: This is a list of the files we expect to get back from the queue. We only want to get
    messages we are actually expecting.
    """
    try:
        message_list = []
        number_messages = len(expected_files)
        while len(message_list) != number_messages:
            for message in output_queue.receive_messages():
                expected_files, message_list, added_message = PopulateMessageList(expected_files, message_list, message.body)
                # Only delete the message if we added it our list, otherwise leave it.
                if added_message:
                    message.delete()
            OKGREEN = '\033[92m'
            ENDC = '\033[0m'
            print("Waiting for the following files to be processed: \n")
            print(OKGREEN)
            print(('\n').join(expected_files))
            print(ENDC)
            print()
            sys.stdout.flush()
            sleep(2)
    except KeyboardInterrupt as e:
        print("Stopping loop even though all messages were not received...")

    return message_list

def main():
    parser = argparse.ArgumentParser(description="A utility to add a list of video files that exist on Amazon S3 from which to extract features.")
    parser.add_argument("input_file", type=str, help="Path to a file that contains a list"
                                                     " of S3 files to process. File must be a comma separated listed of path,classification.")
    parser.add_argument('--queue_name', type=str, default='default', help='Name of the queue to use for adding the messages. '
                                                                          'If the queue does not already exist, one is created.'
                                                                          ' The default queue is named \'default\'.')

    args = parser.parse_args()

    input_file = os.path.abspath(args.input_file)
    queue_name = args.queue_name

    print("Processing file = ", input_file)
    print("Using queue = ", queue_name, " for putting video paths")

    # Set up the queue for receiving the output of the compute nodes
    output_queue = InitializeOutputQueue("feature_queue")

    df = pd.read_csv(input_file)
    # Create the messages to send to sqs
    batch_messages = mt.create_messages_from_df(df)

    # Create a queue or retrieve one with the same name
    sqs = boto3.resource('sqs')
    queue = sqs.create_queue(QueueName=queue_name)

    # Send the messages to SQS
    for messages in batch_messages:
        response = queue.send_messages(Entries=messages)
        print("Errors received from response: ", response.get('Failed'))

    # Wait for input queue to become empty
    num_messages = GetMessagesInQueue(sqs, queue_name)

    start = time.time()
    zero_counter = 0
    while zero_counter < 3:
        print("There are ", num_messages, " remaining in queue")
        sleep(1)
        num_messages = GetMessagesInQueue(sqs, queue_name)
        if num_messages == 0:
            zero_counter += 1
        else:
            zero_counter = 0

    stop = time.time()
    print("It took ", stop - start, " seconds for all messages to be ingested.")

    # Retrieve messages from the sqs queue
    filenames_to_receive = list(df['path'])

    # Poll until all the messages have been received.
    print("Wating for all messages to be processed...")
    message_list = ReceiveNMessagesFromOutputQueue(output_queue, filenames_to_receive)
    print("Done!")

    stop = time.time()
    print("It took ", stop - start, "seconds to process ", len(message_list), " videos")

    # save messages to a file
    header = ['Filename,CenX_CDF,CenY_CDF,Orient_CDF,Histo_CDF,Motion_mag_CDF,Motion_orient_CDF,Classification\n']
    message_list = header + message_list
    message_list = ('').join(message_list)
    with open('output.csv', 'wb') as f:
        f.write(message_list)

if __name__=='__main__':
    main()<commit_msg>Prettify the printing<commit_after>#!/bin/env python

from __future__ import print_function

import argparse
import os
import pandas as pd
import message_type as mt
import boto3
from time import sleep
import time
import sys

def GetMessagesInQueue(sqs_resource, queue_name):
    # Get the queue. This returns an SQS.Queue instance
    queue = sqs_resource.get_queue_by_name(QueueName=queue_name)

    # You can now access identifiers and attributes
    num_messages = queue.attributes.get('ApproximateNumberOfMessages')
    return int(num_messages)

def GetNumberOfOutputs(s3_bucket, key):
    return sum(1 for _ in s3_bucket.objects.filter(Prefix=key))

def DeleteMessagesInBucket(s3_bucket, key):
    for key in s3_bucket.list(prefix=key):
        key.delete()

def DownloadMessagesInBucket(s3_bucket, key):
    s3 = boto3.resource('s3')
    for obj in s3_bucket.objects.filter(Prefix=key):
        s3.Object(s3_bucket.name, obj.key).download_file(obj.key)

def DeleteMessagesInBucket(s3_bucket, key):
    s3 = boto3.resource('s3')
    for obj in s3_bucket.objects.filter(Prefix=key):
        s3.Object(s3_bucket.name, obj.key).delete()

def InitializeOutputQueue(queue_name):
    sqs = boto3.resource('sqs')
    output_queue = sqs.create_queue(QueueName=queue_name)

    try:
        output_queue.purge()
    except:
        print("Already purged...")
    return output_queue

def PopulateMessageList(expected_files, message_list, current_message):
    # Just need to assume that first value is the filename
    message_filename = current_message.split(',')[0]
    for idx, filename in enumerate(expected_files):
        if filename == message_filename:
            message_list.append(current_message)
            del expected_files[idx]
            return (expected_files, message_list, True)
    return (expected_files, message_list, False)

def ReceiveNMessagesFromOutputQueue(output_queue, expected_files):
    """
    Function that polls the queue for messages.
    expected_files: This is a list of the files we expect to get back from the queue. We only want to get
    messages we are actually expecting.
    """
    try:
        message_list = []
        number_messages = len(expected_files)
        while len(message_list) != number_messages:
            for message in output_queue.receive_messages():
                expected_files, message_list, added_message = PopulateMessageList(expected_files, message_list, message.body)
                # Only delete the message if we added it our list, otherwise leave it.
                if added_message:
                    message.delete()
            OKGREEN = '\033[92m'
            ENDC = '\033[0m'
            print("Waiting for the following files to be processed:")
            print(OKGREEN, end="")
            print('\n\t', end="")
            print(('\n\t').join(expected_files))
            print(ENDC, end="")
            print()
            sys.stdout.flush()
            sleep(5)
    except KeyboardInterrupt as e:
        print("Stopping loop even though all messages were not received...")

    return message_list

def main():
    parser = argparse.ArgumentParser(description="A utility to add a list of video files that exist on Amazon S3 from which to extract features.")
    parser.add_argument("input_file", type=str, help="Path to a file that contains a list"
                                                     " of S3 files to process. File must be a comma separated listed of path,classification.")
    parser.add_argument('--queue_name', type=str, default='default', help='Name of the queue to use for adding the messages. '
                                                                          'If the queue does not already exist, one is created.'
                                                                          ' The default queue is named \'default\'.')

    args = parser.parse_args()

    input_file = os.path.abspath(args.input_file)
    queue_name = args.queue_name

    print("Processing file = ", input_file)
    print("Using queue = ", queue_name, " for putting video paths")

    # Set up the queue for receiving the output of the compute nodes
    output_queue = InitializeOutputQueue("feature_queue")

    df = pd.read_csv(input_file)
    # Create the messages to send to sqs
    batch_messages = mt.create_messages_from_df(df)

    # Create a queue or retrieve one with the same name
    sqs = boto3.resource('sqs')
    queue = sqs.create_queue(QueueName=queue_name)

    # Send the messages to SQS
    for messages in batch_messages:
        response = queue.send_messages(Entries=messages)
        print("Errors received from response: ", response.get('Failed'))

    # Wait for input queue to become empty
    num_messages = GetMessagesInQueue(sqs, queue_name)

    start = time.time()
    zero_counter = 0
    while zero_counter < 3:
        print("There are ", num_messages, " remaining in queue")
        sleep(1)
        num_messages = GetMessagesInQueue(sqs, queue_name)
        if num_messages == 0:
            zero_counter += 1
        else:
            zero_counter = 0

    stop = time.time()
    print("It took ", stop - start, " seconds for all messages to be ingested.")

    # Retrieve messages from the sqs queue
    filenames_to_receive = list(df['path'])

    # Poll until all the messages have been received.
    print("Wating for all messages to be processed...")
    message_list = ReceiveNMessagesFromOutputQueue(output_queue, filenames_to_receive)
    print("Done!")

    stop = time.time()
    print("It took ", stop - start, "seconds to process ", len(message_list), " videos")

    # save messages to a file
    header = ['Filename,CenX_CDF,CenY_CDF,Orient_CDF,Histo_CDF,Motion_mag_CDF,Motion_orient_CDF,Classification\n']
    message_list = header + message_list
    message_list = ('').join(message_list)
    with open('output.csv', 'wb') as f:
        f.write(message_list)

if __name__=='__main__':
    main()<|endoftext|>


<commit_before><commit_msg>new migration<commit_after><|endoftext|>