Beispiel #1
0
class TemplateContent(BaseContentMixin):

    """
    Model for template related content
    BaseContentMixin is Polymorphic, allowing you to get all contents within few SQL queries (one per child models)

    Attributes:
        name (CharField): If you need to name your template for UX sugar
        path (CharField): Your template path, as used in "render" or include/extend
    """

    template = models.ForeignKey(
        "stack_it.Template",
        verbose_name=_("Template"),
        related_name="contents",
        on_delete=models.CASCADE,
    )
    objects = PolymorphicManager()

    class Meta:

        verbose_name = _("Template")
        verbose_name_plural = _("Template")
        unique_together = (("template", "key"),)

    def __str__(self):
        return self.key
Beispiel #2
0
class BaseProduct(PolymorphicModel):
    name = models.CharField(verbose_name='Name/model', max_length=30)

    price = models.FloatField(verbose_name='Price')
    quantity = models.IntegerField(verbose_name='Quantity',
                                   validators=[MinValueValidator(0)])

    recommended = models.BooleanField(
        default=False)  # recommended products are show on home page

    additional_spec = models.CharField(
        verbose_name='Additional specifications',
        max_length=200,
        blank=True,
        null=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = PolymorphicManager()

    class Meta:
        db_table = 'products'
        verbose_name = 'Base Product'
        verbose_name_plural = 'Base Products'

    def __str__(self):
        return self.name
Beispiel #3
0
class Message(PolymorphicModel):
    sender = models.ForeignKey(get_user_model(),
                               verbose_name=_('sender'),
                               related_name='sent_messages',
                               on_delete=models.PROTECT,
                               help_text='The user who authored the message')
    parent = models.ForeignKey(
        'self',
        verbose_name=_('parent'),
        related_name='replies',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text='The main message in a message thread')
    content = models.TextField(_('content'))

    # Default fields. Used for record-keeping.
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)
    slug = models.SlugField(_('slug'),
                            max_length=250,
                            unique=True,
                            editable=False,
                            blank=True)
    created_at = models.DateTimeField(_('created at'),
                                      auto_now_add=True,
                                      editable=False)
    updated_at = models.DateTimeField(_('uploaded at'),
                                      auto_now=True,
                                      editable=False)

    objects = PolymorphicManager()
    custom_objects = MessageManager()

    class Meta:
        db_table = 'vestlus_messages'
        indexes = [models.Index(fields=['created_at', 'sender', 'parent'])]
        ordering = ['-created_at', 'sender']

    @property
    def preview(self):
        return f'{truncatewords(self.content, 20)}...'

    @property
    def avatar(self):
        try:
            return self.sender.photo.url
        except AttributeError:
            return 'https://github.com/octocat.png'

    def save(self, *args, **kwargs):
        self.slug = slugify(f'{str(self.uuid)[-12:]}')
        super().save(*args, **kwargs)

    def __str__(self):
        return f'{self.slug}'

    def get_absolute_url(self):
        return reverse('vestlus:message-detail', kwargs={'slug': self.slug})
class Base(AdminLinkMixin, PolymorphicModel):
    slug = models.SlugField()
    objects = PolymorphicManager()

    if django.VERSION < (2, 0):

        class Meta:
            manager_inheritance_from_future = True

    def __str__(self):
        return "Base %s" % self.slug
Beispiel #5
0
class PageContent(BaseContentMixin):

    """
    Model for page related content
    BaseContentMixin is Polymorphic, allowing you to get all contents within few SQL queries (one per child models)

    Attributes:
        page (ForeignKey): Page instance where content belongs
    """

    page = models.ForeignKey(
        "stack_it.Page",
        verbose_name=_("Page"),
        related_name="contents",
        on_delete=models.CASCADE,
    )
    objects = PolymorphicManager()

    class Meta:
        verbose_name = _("Page Content")
        verbose_name_plural = _("Page Contents")
        unique_together = (("page", "key"),)
Beispiel #6
0
class MROBase3(models.Model):
    objects = PolymorphicManager()
Beispiel #7
0
class ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A):
    my_objects = MyManager()
    objects = PolymorphicManager()
    field4 = models.CharField(max_length=10)
Beispiel #8
0
class Hook(PolymorphicModel, BaseConcurrentModel):
    name = models.CharField(primary_key=True, max_length=255)
    objects = PolymorphicManager()
Beispiel #9
0
class ProductCard(PolymorphicModel, OfferPage, Image, Weighable, Dimensional):
    class Meta:
        # abstract = True
        pass

    # Custom managers
    objects = PolymorphicManager()
    public = PublicProductCardManager()

    # KEY-CLASSES
    attributevalues_relation_class = None
    attributevalue_class = None
    product_model_class = None

    # KEYS AND M2M
    attribute_values = None
    product_model = None

    # Image
    upload_image_to = None
    image_key_attribute = 'get_image_name'

    # Search
    update_search = False

    vendor_code = models.CharField(verbose_name="Артикул",
                                   max_length=1024,
                                   db_index=True,
                                   unique=True,
                                   validators=[
                                       RegexValidator(
                                           regex=r'^[-_a-z\d]+$',
                                           message='vendor_code valid error',
                                       )
                                   ])

    product_type = models.CharField(
        verbose_name="Тип продукта",
        max_length=128,
        choices=(("", "Не задано"), ("BACKPACK", "BACKPACK"), ("BAG", "BAG"),
                 ("SUITCASE", "SUITCASE"), ("PURSE", "PURSE")),
        default="",
        blank=True,
    )

    @property
    def data_for_delivery(self):
        result = {
            "product_type": self.product_type,
            "price": self.price,
            "purchase_price": self.purchase_price,
            "vendor": self.vendor
        }
        if self.weigh:
            result['weigh'] = math.ceil(self.weigh / 1000)
        if self.dimensions:
            result['dimensions'] = self.dimensions
        return result

    product_aditional_image_class = None

    @property
    def get_image_name(self):
        return self.model.replace('/', '-')

    model = models.CharField(verbose_name="Модель",
                             max_length=1024,
                             db_index=True)

    def get_url(self):
        return self.slug

    @disallowed_before_creation
    def add_attribute_value(self, attribute_value_instance):
        if attribute_value_instance.pk:
            try:
                self.attributevalues_relation_class.objects.create(
                    product=self, attribute_value=attribute_value_instance)
            except IntegrityError:
                pass
        else:
            raise DisallowedBeforeCreationException(
                'attributes_value_instance must be creatied')

    @disallowed_before_creation
    def del_attribute_value(self, attribute_value_instance):
        if attribute_value_instance.pk:
            try:
                self.attributevalues_relation_class.objects.get(
                    product=self,
                    attribute_value=attribute_value_instance).delete()
            except ObjectDoesNotExist:
                pass
        else:
            raise DisallowedBeforeCreationException(
                'attribute_value_instance must be creatied')

    @disallowed_before_creation
    def clear_attribute_value(self):
        self.attributevalues_relation_class.objects.filter(
            product=self).delete()

    @property
    @disallowed_before_creation
    def attributes(self):
        result = OrderedDict()
        for av in self.attribute_values.select_related().order_by(
                '-attribute__order', '-order'):
            result[av.attribute] = result.get(av.attribute, []) + [av]
        return result

    @property
    @disallowed_before_creation
    def additional_images(self):
        return self.product_aditional_image_class.objects.filter(
            product=self).order_by('order', 'id')

    @property
    def public_id(self):
        return self.get_public_id()

    @property
    def has_photo(self):
        return self.image != self.image.field.default

    @property
    @disallowed_before_creation
    def photo_checksums(self):
        checksums = set()
        images = self.product_aditional_image_class.objects.filter(
            product=self, )
        if self.has_photo:
            checksums.add(md5_file_checksum(self.image.path))
        for image in images:
            checksums.add(md5_file_checksum(image.image.path))
        return checksums

    def generate_image_from_main(self):
        max_order = self.product_aditional_image_class.objects.filter(
            product=self, ).aggregate(Max('order'))['order__max']
        if max_order is None:
            max_order = 0
        else:
            max_order += 1
        instance = self.product_aditional_image_class(
            product=self,
            image=self.image,
            order=max_order,
        )
        instance.save()

    def replace_main_image_by_file(self, image_file):
        self.generate_image_from_main()
        self.image = image_file
        update_search = not settings.DEBUG
        self.save(update_search=update_search)

    def replace_main_image_by_additional(self, image_id):
        try:
            instance = self.product_aditional_image_class.objects.get(
                id=image_id)
            self.generate_image_from_main()
            self.image = instance.image
            update_search = not settings.DEBUG
            self.save(update_search=update_search)
            instance.delete()
        except self.product_aditional_image_class.DoesNotExist:
            pass

    def add_photo(self, filename):
        fp = open(filename, 'rb')
        img_file = File(fp)
        if self.has_photo:
            instance = self.product_aditional_image_class(
                product=self,
                image=img_file,
            )
            instance.save()
        else:
            self.image = img_file
            self.save()
        fp.close()

    def get_public_id(self):
        msg = "Method get_public_id() must be implemented by subclass: `{}`"
        raise NotImplementedError(msg.format(self.__class__.__name__))

    def get_scoring(self):
        scoring = 0
        if self.has_photo:
            if self.scoring != 0:
                scoring = self.scoring
            else:
                scoring = random.randint(100, 150)
        return scoring

    def get_image_url(self):
        return self.image.url

    def get_modifications(self):
        if self.product_model is not None:
            return self.product_model.products.exclude(id=self.id)
        return []

    @property
    def modifications(self):
        return self.get_modifications()

    def __str__(self):
        return 'Product card: {0}'.format(self.slug)

    def save(self, update_search=False, *args, **kwargs):
        self.update_search = update_search
        self.scoring = self.get_scoring()

        super(ProductCard, self).save(*args, **kwargs)
class BaseResource(PolymorphicModel, BaseConcurrentModel):  #
    name = models.CharField(u'资源名', max_length=64, blank=False, null=False)
    departments = models.ManyToManyField(Department, blank=True)
    objects = PolymorphicManager()
Beispiel #11
0
class MROBase3(models.Model):
    base_3_id = models.AutoField(
        primary_key=True
    )  # make sure 'id' field doesn't clash, detected by Django 1.11
    objects = PolymorphicManager()
Beispiel #12
0
class Source(PolymorphicModel):
    """
    A project source.
    """

    project = models.ForeignKey(
        Project,
        null=True,
        blank=True,
        on_delete=NON_POLYMORPHIC_CASCADE,
        related_name="sources",
    )

    address = models.TextField(
        null=False,
        blank=False,
        help_text="The address of the source. e.g. github://org/repo/subpath",
    )

    path = models.TextField(
        null=True,
        blank=True,
        help_text=
        "The file or folder name, or path, that the source is mapped to.",
    )

    order = models.PositiveIntegerField(
        null=True,
        blank=True,
        help_text="The order that the source should be pulled into the project "
        "(allows explicit overwriting of one source by another).",
    )

    creator = models.ForeignKey(
        User,
        null=True,  # Should only be null if the creator is deleted
        blank=True,
        on_delete=models.SET_NULL,
        related_name="sources_created",
        help_text="The user who created the source.",
    )

    created = models.DateTimeField(
        auto_now_add=True, help_text="The time the source was created.")

    updated = models.DateTimeField(
        auto_now=True, help_text="The time the source was last changed.")

    subscription = models.JSONField(
        null=True,
        blank=True,
        help_text=
        "Information from the source provider (e.g. Github, Google) when a watch subscription was created.",
    )

    jobs = models.ManyToManyField(
        Job,
        help_text=
        "Jobs associated with this source. e.g. pull, push or convert jobs.",
    )

    # The default object manager which will fetch data from multiple
    # tables (one query for each type of source)
    objects = PolymorphicManager()

    # An additional manager which will only fetch data from the `Source`
    # table.
    objects_base = models.Manager()

    class Meta:
        constraints = [
            # Constraint to ensure that sources are not duplicated within a
            # project. Note that sources can share the same `path` e.g.
            # two sources both targetting a `data` folder.
            models.UniqueConstraint(fields=["project", "address"],
                                    name="%(class)s_unique_project_address")
        ]

    def __str__(self) -> str:
        return self.address

    @property
    def type_class(self) -> str:
        """
        Get the name the class of a source instance.

        If this is an instance of a derived class then returns that class name.
        Otherwise, fetches the name of the model class based on the `polymorphic_ctype_id`
        (with caching) and then cases it properly.
        """
        if self.__class__.__name__ != "Source":
            return self.__class__.__name__

        all_lower = ContentType.objects.get_for_id(
            self.polymorphic_ctype_id).model
        for source_type in SourceTypes:
            if source_type.name.lower() == all_lower:
                return source_type.name
        return ""

    @property
    def type_name(self) -> str:
        """
        Get the name of the type of a source instance.

        The `type_name` is intended for use in user interfaces.
        This base implementation simply drops `Source` from the end
        of the name. Can be overridden in derived classes if
        necessary.
        """
        return self.type_class[:-6]

    @staticmethod
    def class_from_type_name(type_name: str) -> Type["Source"]:
        """Find the class matching the type name."""
        for source_type in SourceTypes:
            if source_type.name.lower().startswith(type_name.lower()):
                return source_type.value

        raise ValueError(
            'Unable to find class matching "{}"'.format(type_name))

    def make_address(self) -> str:
        """
        Create a human readable string representation of the source instance.

        These are intended to be URL-like human-readable and -writable shorthand
        representations of sources (e.g. `github://org/repo/sub/path`; `gdoc://378yfh2yg362...`).
        They are used in admin lists and in API endpoints to allowing quick
        specification of a source (e.g. for filtering).
        Should be parsable by the `parse_address` method of each subclass.
        """
        return "{}://{}".format(self.type_name.lower(), self.id)

    @staticmethod
    def coerce_address(address: Union[SourceAddress, str]) -> SourceAddress:
        """
        Coerce a string into a `SourceAddress`.

        If the `address` is already an instance of `SourceAddress` it
        will be returned unchanged. Otherwise, the `parse_address`
        method of each source type (ie. subclass of `Source`) will
        be called. The first class that returns a `SourceAddress` wins.
        """
        if isinstance(address, SourceAddress):
            return address

        for source_type in SourceTypes:
            result = source_type.value.parse_address(address)
            if result is not None:
                return result

        raise ValueError('Unable to parse source address "{}"'.format(address))

    @classmethod
    def parse_address(cls,
                      address: str,
                      naked: bool = False,
                      strict: bool = False) -> Optional[SourceAddress]:
        """
        Parse a string into a `SourceAddress`.

        This default implementation just matches the default string
        representation of a source, as generated by `Source.__str__`.
        Derived classes should override this method to match their own
        `__str__` method.
        """
        type_name = cls.__name__[:-6]
        if address.startswith(type_name.lower() + "://"):
            return SourceAddress(type_name)

        if strict:
            raise ValidationError(
                "Invalid source identifier: {}".format(address))

        return None

    @staticmethod
    def query_from_address(
        address_or_string: Union[SourceAddress, str],
        prefix: Optional[str] = None,
    ) -> models.Q:
        """
        Create a query object for a source address.

        Given a source address, constructs a Django `Q` object which
        can be used in ORM queries. Use the `prefix` argument
        when you want to use this function for a related field. For example,

            Source.query_from_address({
                "type": "Github",
                "repo": "org/repo",
                "subpath": "folder"
            }, prefix="sources")

        is equivalent to :

            Q(
                sources__githubsource__repo="org/repo",
                sources__githubsource__subpath="folder"
            )
        """
        address = Source.coerce_address(address_or_string)

        front = ("{}__".format(prefix)
                 if prefix else "") + address.type.__name__.lower()
        kwargs = dict([("{}__{}".format(front, key), value)
                       for key, value in address.items()])
        return models.Q(**kwargs)

    @staticmethod
    def from_address(address_or_string: Union[SourceAddress, str],
                     **other) -> "Source":
        """
        Create a source instance from a source address.

        Given a source address, creates a new source instance of the
        specified `type` with fields set from the address. The `other`
        parameter can be used to set additional fields on the created source.
        """
        address = Source.coerce_address(address_or_string)
        return address.type.objects.create(**address, **other)

    def to_address(self) -> SourceAddress:
        """
        Create a source address from a source instance.

        The inverse of `Source.from_address`. Used primarily
        to create a pull job for a source.
        """
        all_fields = [field.name for field in self._meta.fields]
        class_fields = [
            name for name in self.__class__.__dict__.keys()
            if name in all_fields
        ]
        return SourceAddress(
            self.type_name,
            **dict([(name, value) for name, value in self.__dict__.items()
                    if name in class_fields]),
        )

    def get_secrets(self, user: User) -> Dict:
        """
        Get any secrets required to pull or push the source.

        This method should be overridden by derived classes to return
        a secrets (e.g. OAuth tokens, API keys) specific to the source
        (if necessary).
        """
        return {}

    def get_url(self, path: Optional[str] = None):
        """
        Create a URL for users to visit the source on the external site.

        path: An optional path to a file within the source (for multi-file
              sources such as GitHub repos).
        """
        raise NotImplementedError

    def get_event_url(self):
        """
        Create a URL for source events to be sent to.

        This is the Webhook URL used by Github, Google and other source
        providers to send event data to when a source has been `watch()`ed.
        """
        return ("https://" + settings.PRIMARY_DOMAIN + reverse(
            "api-projects-sources-event",
            kwargs=dict(project=self.project.id, source=self.id),
        ))

    def pull(self, user: Optional[User] = None) -> Job:
        """
        Pull the source to the filesystem.

        Creates a job, and adds it to the source's `jobs` list.
        """
        source = self.to_address()
        source["type"] = source.type_name

        description = "Pull {0}"
        if self.type_class == "UploadSource":
            description = "Collect {0}"
        description = description.format(self.address)

        job = Job.objects.create(
            project=self.project,
            creator=user or self.creator,
            method=JobMethod.pull.value,
            params=dict(source=source, path=self.path),
            description=description,
            secrets=self.get_secrets(user),
            **Job.create_callback(self, "pull_callback"),
        )
        self.jobs.add(job)
        return job

    @transaction.atomic
    def pull_callback(self, job: Job):
        """
        Update the files associated with this source.
        """
        result = job.result
        if not result:
            return

        from projects.models.files import File, get_modified

        # All existing files for the source are made "non-current" (i.e. will not
        # be displayed in project working directory but are retained for history)
        File.objects.filter(project=self.project,
                            source=self).update(current=False,
                                                updated=timezone.now())

        # Do a batch insert of files. This is much faster when there are a lot of file
        # than inserting each file individually.
        File.objects.bulk_create([
            File(
                project=self.project,
                path=path,
                job=job,
                source=self,
                updated=timezone.now(),
                modified=get_modified(info),
                size=info.get("size"),
                mimetype=info.get("mimetype"),
                encoding=info.get("encoding"),
                fingerprint=info.get("fingerprint"),
            ) for path, info in result.items()
        ])

        # Asynchronously check whether the project's image needs to be updated given
        # that there are updated files.
        update_image_for_project.delay(project_id=self.project.id)

    def extract(self,
                review,
                user: Optional[User] = None,
                filters: Optional[Dict] = None) -> Job:
        """
        Extract a review from a project source.

        Creates a job, and adds it to the source's `jobs` list.
        Note: the jobs callback is `Review.extract_callback`.
        """
        source = self.to_address()
        source["type"] = source.type_name

        description = "Extract review from {0}"
        description = description.format(self.address)

        job = Job.objects.create(
            project=self.project,
            creator=user or self.creator,
            method=JobMethod.extract.value,
            params=dict(source=source, filters=filters),
            description=description,
            secrets=self.get_secrets(user),
            **Job.create_callback(review, "extract_callback"),
        )
        self.jobs.add(job)
        return job

    def push(self) -> Job:
        """
        Push from the filesystem to the source.

        Creates a `Job` having the `push` method and a dictionary of `source`
        attributes sufficient to push it. This may include authentication tokens.
        """
        raise NotImplementedError(
            "Push is not implemented for class {}".format(
                self.__class__.__name__))

    def watch(self, user: User):
        """
        Create a subscription to listen to events for the source.
        """
        raise NotImplementedError(
            "Watch is not implemented for class {}".format(
                self.__class__.__name__))

    def unwatch(self, user: User):
        """
        Remove a subscription to listen to events for the source.
        """
        raise NotImplementedError(
            "Unwatch is not implemented for class {}".format(
                self.__class__.__name__))

    def event(self, data: dict, headers: dict = {}):
        """
        Handle an event notification.

        Passes on the event to the project's event handler with
        this source added to the context.
        """
        self.project.event(data=data, source=self)

    def preview(self, user: User) -> Job:
        """
        Generate a HTML preview of a source.

        Creates a `series` job comprising a
        `pull` job followed by a `convert` job.
        """
        preview = Job.objects.create(project=self.project,
                                     creator=user,
                                     method="series")
        preview.children.add(self.pull(user))
        preview.children.add(self.convert(user, to="html"))
        return preview

    # Properties related to jobs. Provide shortcuts for
    # obtaining info such as the files created in the last pull,
    # or the time of the last push

    @property
    def is_active(self) -> bool:
        """
        Is the source currently active.

        A source is considered active if it has an active job.
        The `since` parameter is included to ignore old, orphaned jobs
        which may have not have had their status updated.
        """
        since = datetime.timedelta(minutes=15)
        return (self.jobs.filter(
            is_active=True, created__gte=timezone.now() - since).count() > 0)

    def get_downstreams(self, current=True):
        """
        Get the downstream files.

        The equivalent of `File.get_downstreams()`.
        """
        return self.files.filter(current=current)

    def get_jobs(self, n=10) -> List[Job]:
        """
        Get the jobs associated with this source.
        """
        return self.jobs.order_by("-created").select_related("creator")[:n]

    def save(self, *args, **kwargs):
        """
        Save the source.

        An override to ensure necessary fields are set.
        """
        if not self.address:
            self.address = self.make_address()

        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """
        Delete the source.

        Override to unwatch a source before deleting it. This avoids the
        source provider continuing to send events for the source
        when it no longer exists.
        """
        if self.subscription:
            # Because we do not have the quest user here, use
            # the source creator for the unwatch and log any
            # exceptions as warnings only.
            try:
                self.unwatch(self.creator)
            except Exception as exc:
                logger.warning(str(exc), exc_info=True)

        return super().delete(*args, **kwargs)