コード例 #1
0
class ShareObjectVersion(models.Model):
    action = models.TextField(max_length=10)
    objects = FuzzyCountManager()

    class Meta:
        abstract = True
        ordering = ('-date_modified', )
コード例 #2
0
class UnusedCeleryTask(TypedModel):
    """Keeping this model around so we have the data to refer back to, if need be.
    """
    STATUS = Choices(
        (0, 'started', _('started')),
        (1, 'retried', _('retried')),
        (2, 'failed', _('failed')),
        (3, 'succeeded', _('succeeded')),
    )

    uuid = models.UUIDField(db_index=True, unique=True)
    name = models.TextField(blank=True, db_index=True)
    args = models.TextField(blank=True)
    kwargs = models.TextField(blank=True)
    timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
    started_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name='started_by',
                                   null=True,
                                   on_delete=models.CASCADE)
    # TODO rename to 'source'
    provider = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 related_name='provider',
                                 null=True,
                                 on_delete=models.CASCADE)
    status = models.IntegerField(choices=STATUS)

    objects = FuzzyCountManager()

    class Meta:
        ordering = ('-timestamp', )
        index_together = ('type', 'name', 'app_label', 'timestamp')
コード例 #3
0
class ExtraData(models.Model, metaclass=ShareObjectMeta):
    data = fields.DateTimeAwareJSONField(default=dict)

    objects = FuzzyCountManager()

    class Meta:
        abstract = False
コード例 #4
0
class ShareObjectVersion(models.Model):
    action = models.TextField(max_length=10)
    objects = FuzzyCountManager()

    class Meta:
        abstract = True
        ordering = ('-date_modified', )
        base_manager_name = 'objects'

    def __repr__(self):
        return '<{type}({id}, of {persistent_id} at {date_modified})>'.format(
            type=type(self).__name__,
            id=self.id,
            persistent_id=self.persistent_id_id,
            date_modified=self.date_modified,
        )
コード例 #5
0
ファイル: change.py プロジェクト: simhaonline/SHARE
class ChangeSet(models.Model):
    STATUS = Choices((0, 'pending', _('pending')),
                     (1, 'accepted', _('accepted')),
                     (2, 'rejected', _('rejected')))

    objects = FuzzyCountManager()

    status = models.IntegerField(choices=STATUS, default=STATUS.pending)
    submitted_at = models.DateTimeField(auto_now_add=True)
    normalized_data = models.ForeignKey(NormalizedData,
                                        on_delete=models.CASCADE)

    _changes_cache = []

    class JSONAPIMeta(BaseJSONAPIMeta):
        pass

    def accept(self, save=True):
        ret = []
        with transaction.atomic():
            self._changes_cache = list(self.changes.all())
            for c in self._changes_cache:
                ret.append(c.accept(save=save))
            self.status = ChangeSet.STATUS.accepted
            if save:
                self.save()
        return ret

    def _resolve_ref(self, ref):
        model = apps.get_model('share', model_name=ref['@type'])
        ct = ContentType.objects.get_for_model(model, for_concrete_model=True)
        try:
            if ref['@id'].startswith('_:'):
                return next(change.target for change in self._changes_cache
                            if change.target_type == ct
                            and change.node_id == ref['@id'] and change.target)
            return model._meta.concrete_model.objects.get(
                pk=IDObfuscator.decode_id(ref['@id']))
        except (StopIteration, model.DoesNotExist) as ex:
            raise Exception(
                'Could not resolve reference {}'.format(ref)) from ex

    def __repr__(self):
        return '<{}({}, {}, {} changes)>'.format(
            self.__class__.__name__, self.STATUS[self.status].upper(),
            self.normalized_data.source, self.changes.count())
コード例 #6
0
ファイル: change.py プロジェクト: simhaonline/SHARE
class Change(models.Model):
    TYPE = Choices((0, 'create', _('create')), (1, 'merge', _('merge')),
                   (2, 'update', _('update')))

    objects = FuzzyCountManager()

    change = JSONField()
    node_id = models.TextField()

    type = models.IntegerField(choices=TYPE, editable=False)
    # The non-concrete type that this change has made
    model_type = models.ForeignKey(ContentType,
                                   related_name='+',
                                   db_index=False,
                                   on_delete=models.CASCADE)

    target_id = models.PositiveIntegerField(null=True)
    target = GenericForeignKey('target_type', 'target_id')
    target_type = models.ForeignKey(ContentType,
                                    related_name='target_%(class)s',
                                    on_delete=models.CASCADE)

    target_version_type = models.ForeignKey(
        ContentType,
        related_name='target_version_%(class)s',
        db_index=False,
        on_delete=models.CASCADE)
    target_version_id = models.PositiveIntegerField(null=True, db_index=False)
    target_version = GenericForeignKey('target_version_type',
                                       'target_version_id')

    change_set = models.ForeignKey(ChangeSet,
                                   related_name='changes',
                                   on_delete=models.CASCADE)

    class JSONAPIMeta(BaseJSONAPIMeta):
        pass

    class Meta:
        ordering = ('pk', )
        indexes = (ConcurrentIndex(fields=['target_id', 'target_type']), )

    def accept(self, save=True):
        # Little bit of blind faith here that all requirements have been accepted
        assert self.change_set.status == ChangeSet.STATUS.pending, 'Cannot accept a change with status {}'.format(
            self.change_set.status)
        logger.debug('Accepting change node ({}, {})'.format(
            ContentType.objects.get_for_id(self.model_type_id), self.node_id))
        try:
            ret = self._accept(save)
        except IntegrityError as e:
            if e.args[0].startswith(
                    'duplicate key value violates unique constraint'):
                raise IngestConflict
            raise

        if save:
            # Psuedo hack, sources.add(...) tries to do some safety checks.
            # Don't do that. We have a database. That is its job. Let it do its job.
            through_meta = ret._meta.get_field(
                'sources').remote_field.through._meta

            with connection.cursor() as cursor:
                cursor.execute(
                    '''
                    INSERT INTO "{0}"
                        ("{1}", "{2}")
                    VALUES
                        (%s, %s)
                    ON CONFLICT DO NOTHING;
                '''.format(
                        through_meta.db_table,
                        through_meta.get_field(
                            ret._meta.concrete_model._meta.model_name).column,
                        through_meta.get_field('shareuser').column,
                    ), (ret.pk, self.change_set.normalized_data.source_id))

            self.save()
        else:
            logger.warning(
                'Calling accept with save=False will not update the sources field'
            )

        return ret

    def _accept(self, save):
        if self.type == Change.TYPE.create:
            return self._create(save=save)
        if self.type == Change.TYPE.update:
            return self._update(save=save)
        return self._merge(save=save)

    def _create(self, save=True):
        resolved_change = self._resolve_change()
        inst = ContentType.objects.get_for_id(
            self.model_type_id).model_class()(change=self, **resolved_change)
        if save:
            inst.save()
        self.target = inst
        return inst

    def _update(self, save=True):
        self.target.change = self

        new_type = self.change.pop('@type', None)
        if new_type:
            self.target.recast(new_type)

        for k, v in self._resolve_change().items():
            setattr(self.target, k, v)

        if save:
            self.target.save()
        return self.target

    def _merge(self, save=True):
        from share.models.base import ShareObject
        assert save is True, 'Cannot perform merge without saving'

        change = self._resolve_change()
        # Find all fields that reference this model
        fields = [
            field.field
            for field in self.target_type.model_class()._meta.get_fields()
            if field.is_relation and not field.many_to_many and field.
            remote_field and issubclass(field.remote_field.model, ShareObject)
            and hasattr(field, 'field')
        ]

        # NOTE: Date is pinned up here to ensure its the same for all changed rows
        date_modified = timezone.now()

        for field in fields:
            # Update all rows in "from"
            # Updates the change, the field in question, the version pin of the field in question
            # and date_modified must be manually updated
            field.model.objects.select_for_update().filter(
                **{
                    field.name + '__in': change['from']
                }).update(
                    **{
                        'change': self,
                        field.name: change['into'],
                        field.name + '_version': change['into'].version,
                        'date_modified': date_modified,
                    })

        # Finally point all from rows' same_as and
        # same_as_version to the canonical model.
        type(change['into']).objects.select_for_update().filter(
            pk__in=[i.pk for i in change['from']]).update(
                change=self,
                same_as=change['into'],
                same_as_version=change['into'].version,
                date_modified=date_modified,
            )

        return change['into']

    def _resolve_change(self):
        change = {}
        for k, v in self.change.items():
            if k == 'extra':
                if not v:
                    continue
                if self.target and self.target.extra:
                    change[k] = self.target.extra
                else:
                    from share.models.base import ExtraData
                    change[k] = ExtraData()
                change[k].change = self
                change[k].data.update(
                    {self.change_set.normalized_data.source.username: v})
                change[k].save()
                change[k + '_version_id'] = change[k].version_id
            elif isinstance(v, dict):
                inst = self.change_set._resolve_ref(v)
                change[k] = inst
                try:
                    change[k + '_version_id'] = inst.version_id
                except AttributeError:
                    # this isn't a ShareObject, no worries
                    pass
            elif isinstance(v, list):
                change[k] = [self.change_set._resolve_ref(r) for r in v]
            else:
                change[k] = v

        if self.target_type.model == 'subject':
            SubjectTaxonomy = apps.get_model('share', 'subjecttaxonomy')
            user = self.change_set.normalized_data.source
            central_synonym = change.get(
                'central_synonym',
                self.target.central_synonym if self.target else None)
            if central_synonym is None and user.username != settings.APPLICATION_USERNAME:
                raise PermissionError(
                    'Only the system user can modify the central subject taxonomy, not {}'
                    .format(user))
            change['taxonomy'], _ = SubjectTaxonomy.objects.get_or_create(
                source=user.source)

        return change