Esempio n. 1
0
class Course(BaseModel):
    teacher = models.ForeignKey(Teacher,
                                on_delete=models.CASCADE,
                                verbose_name="讲师")
    course_org = models.ForeignKey(CourseOrg,
                                   null=True,
                                   blank=True,
                                   on_delete=models.CASCADE,
                                   verbose_name="课程机构")
    name = models.CharField(verbose_name="课程名", max_length=50)
    desc = models.CharField(verbose_name="课程描述", max_length=300)
    learn_times = models.IntegerField(default=0, verbose_name="学习时长(分钟数)")
    degree = models.CharField(verbose_name="难度",
                              choices=(("cj", "初级"), ("zj", "中级"), ("gj",
                                                                    "高级")),
                              max_length=2)
    students = models.IntegerField(default=0, verbose_name='学习人数')
    fav_nums = models.IntegerField(default=0, verbose_name='收藏人数')
    click_nums = models.IntegerField(default=0, verbose_name="点击数")
    notice = models.CharField(verbose_name="课程公告", max_length=300, default="")
    category = models.CharField(default=u"后端开发",
                                max_length=20,
                                verbose_name="课程类别")
    tag = models.CharField(default="", verbose_name="课程标签", max_length=10)
    youneed_know = models.CharField(default="",
                                    max_length=300,
                                    verbose_name="课程须知")
    teacher_tell = models.CharField(default="",
                                    max_length=300,
                                    verbose_name="老师告诉你")
    is_classics = models.BooleanField(default=False, verbose_name="是否经典")
    detail = UEditorField(verbose_name="课程详情",
                          width=600,
                          height=300,
                          imagePath="courses/ueditor/images/",
                          filePath="courses/ueditor/files/",
                          default="")
    is_banner = models.BooleanField(default=False, verbose_name="是否广告位")
    image = StdImageField(
        max_length=100,
        upload_to=UploadToUUID(path=datetime.now().strftime('courses/%Y/%m')),
        verbose_name=u"封面图",
        variations={'thumbnail': {
            'width': 100,
            'height': 75
        }})

    class Meta:
        verbose_name = "课程信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

    def lesson_nums(self):
        return self.lesson_set.all().count()

    def show_image(self):
        if self.image:
            href = self.image.url  # 点击后显示的放大图片
            src = self.image.thumbnail.url  # 页面显示的缩略图
            # 插入html代码
            image_html = '<a href="%s" target="_blank" title="传图片"><img alt="" src="%s"/>' % (
                href, src)
            return image_html
        else:
            return u'上传图片'

    show_image.short_description = "封面图"
    show_image.allow_tags = True

    def go_to(self):
        from django.utils.safestring import mark_safe
        return mark_safe("<a href='/course/{}'>跳转</a>".format(self.id))

    go_to.short_description = "跳转"
Esempio n. 2
0
class Mentor(CommonInfo):

    user = models.ForeignKey(
        CDCUser,
        on_delete=models.CASCADE,
    )

    bio = models.TextField(
        blank=True,
        null=True,
    )

    is_active = models.BooleanField(default=True, )

    background_check = models.BooleanField(default=False, )

    is_public = models.BooleanField(default=False, )

    avatar = StdImageField(
        upload_to=generate_filename,
        blank=True,
        variations={
            'thumbnail': {
                'width': 500,
                'height': 500,
                'crop': True,
            }
        },
    )

    avatar_approved = models.BooleanField(default=False, )

    birthday = models.DateTimeField(
        blank=False,
        null=True,
    )

    gender = models.CharField(
        max_length=255,
        blank=False,
        null=True,
    )

    race_ethnicity = models.ManyToManyField(
        RaceEthnicity,
        blank=False,
    )

    work_place = models.CharField(
        max_length=255,
        blank=True,
        null=True,
    )

    phone = models.CharField(
        max_length=255,
        blank=True,
        null=True,
    )

    home_address = models.CharField(
        max_length=255,
        blank=True,
        null=True,
    )

    class Meta:
        verbose_name = _("mentors")
        verbose_name_plural = _("mentors")

    def __str__(self):
        return f"{self.user.first_name} {self.user.last_name}"

    def full_name(self):
        return f"{self.user.first_name} {self.user.last_name}"

    def save(self, *args, **kwargs):
        if self.pk is not None:
            orig = Mentor.objects.get(pk=self.pk)
            if orig.avatar != self.avatar:
                self.avatar_approved = False

        super(Mentor, self).save(*args, **kwargs)

    def get_approve_avatar_url(self):
        return reverse('mentor-approve-avatar', args=[str(self.id)])

    def get_reject_avatar_url(self):
        return reverse('mentor-reject-avatar', args=[str(self.id)])

    def get_absolute_url(self):
        return reverse('mentor-detail', args=[str(self.id)])

    @property
    def first_name(self):
        return self.user.first_name

    @property
    def last_name(self):
        return self.user.last_name
Esempio n. 3
0
class Company(models.Model):
    """ A Company object represents an external company.
    It may be a supplier or a customer or a manufacturer (or a combination)

    - A supplier is a company from which parts can be purchased
    - A customer is a company to which parts can be sold
    - A manufacturer is a company which manufactures a raw good (they may or may not be a "supplier" also)


    Attributes:
        name: Brief name of the company
        description: Longer form description
        website: URL for the company website
        address: Postal address
        phone: contact phone number
        email: contact email address
        link: Secondary URL e.g. for link to internal Wiki page
        image: Company image / logo
        notes: Extra notes about the company
        is_customer: boolean value, is this company a customer
        is_supplier: boolean value, is this company a supplier
        is_manufacturer: boolean value, is this company a manufacturer
    """

    name = models.CharField(max_length=100,
                            blank=False,
                            unique=True,
                            help_text=_('Company name'),
                            verbose_name=_('Company name'))

    description = models.CharField(max_length=500,
                                   verbose_name=_('Company description'),
                                   help_text=_('Description of the company'))

    website = models.URLField(blank=True,
                              verbose_name=_('Website'),
                              help_text=_('Company website URL'))

    address = models.CharField(max_length=200,
                               verbose_name=_('Address'),
                               blank=True,
                               help_text=_('Company address'))

    phone = models.CharField(max_length=50,
                             verbose_name=_('Phone number'),
                             blank=True,
                             help_text=_('Contact phone number'))

    email = models.EmailField(blank=True,
                              verbose_name=_('Email'),
                              help_text=_('Contact email address'))

    contact = models.CharField(max_length=100,
                               verbose_name=_('Contact'),
                               blank=True,
                               help_text=_('Point of contact'))

    link = InvenTreeURLField(
        blank=True, help_text=_('Link to external company information'))

    image = StdImageField(
        upload_to=rename_company_image,
        null=True,
        blank=True,
        variations={'thumbnail': (128, 128)},
        delete_orphans=True,
    )

    notes = MarkdownxField(blank=True)

    is_customer = models.BooleanField(
        default=False, help_text=_('Do you sell items to this company?'))

    is_supplier = models.BooleanField(
        default=True, help_text=_('Do you purchase items from this company?'))

    is_manufacturer = models.BooleanField(
        default=False, help_text=_('Does this company manufacture parts?'))

    def __str__(self):
        """ Get string representation of a Company """
        return "{n} - {d}".format(n=self.name, d=self.description)

    def get_absolute_url(self):
        """ Get the web URL for the detail view for this Company """
        return reverse('company-detail', kwargs={'pk': self.id})

    def get_image_url(self):
        """ Return the URL of the image for this company """

        if self.image:
            return getMediaUrl(self.image.url)
        else:
            return getBlankImage()

    def get_thumbnail_url(self):
        """ Return the URL for the thumbnail image for this Company """

        if self.image:
            return getMediaUrl(self.image.thumbnail.url)
        else:
            return getBlankThumbnail()

    @property
    def manufactured_part_count(self):
        """ The number of parts manufactured by this company """
        return self.manufactured_parts.count()

    @property
    def has_manufactured_parts(self):
        return self.manufactured_part_count > 0

    @property
    def supplied_part_count(self):
        """ The number of parts supplied by this company """
        return self.supplied_parts.count()

    @property
    def has_supplied_parts(self):
        """ Return True if this company supplies any parts """
        return self.supplied_part_count > 0

    @property
    def parts(self):
        """ Return SupplierPart objects which are supplied or manufactured by this company """
        return SupplierPart.objects.filter(
            Q(supplier=self.id) | Q(manufacturer=self.id))

    @property
    def part_count(self):
        """ The number of parts manufactured (or supplied) by this Company """
        return self.parts.count()

    @property
    def has_parts(self):
        return self.part_count > 0

    @property
    def stock_items(self):
        """ Return a list of all stock items supplied or manufactured by this company """
        stock = apps.get_model('stock', 'StockItem')
        return stock.objects.filter(
            Q(supplier_part__supplier=self.id)
            | Q(supplier_part__manufacturer=self.id)).all()

    @property
    def stock_count(self):
        """ Return the number of stock items supplied or manufactured by this company """
        return self.stock_items.count()

    def outstanding_purchase_orders(self):
        """ Return purchase orders which are 'outstanding' """
        return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN)

    def pending_purchase_orders(self):
        """ Return purchase orders which are PENDING (not yet issued) """
        return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING)

    def closed_purchase_orders(self):
        """ Return purchase orders which are not 'outstanding'

        - Complete
        - Failed / lost
        - Returned
        """

        return self.purchase_orders.exclude(
            status__in=PurchaseOrderStatus.OPEN)

    def complete_purchase_orders(self):
        return self.purchase_orders.filter(status=PurchaseOrderStatus.COMPLETE)

    def failed_purchase_orders(self):
        """ Return any purchase orders which were not successful """

        return self.purchase_orders.filter(
            status__in=PurchaseOrderStatus.FAILED)
Esempio n. 4
0
class Part(models.Model):
    """ The Part object represents an abstract part, the 'concept' of an actual entity.

    An actual physical instance of a Part is a StockItem which is treated separately.

    Parts can be used to create other parts (as part of a Bill of Materials or BOM).

    Attributes:
        name: Brief name for this part
        variant: Optional variant number for this part - Must be unique for the part name
        category: The PartCategory to which this part belongs
        description: Longer form description of the part
        keywords: Optional keywords for improving part search results
        IPN: Internal part number (optional)
        revision: Part revision
        is_template: If True, this part is a 'template' part and cannot be instantiated as a StockItem
        link: Link to an external page with more information about this part (e.g. internal Wiki)
        image: Image of this part
        default_location: Where the item is normally stored (may be null)
        default_supplier: The default SupplierPart which should be used to procure and stock this part
        minimum_stock: Minimum preferred quantity to keep in stock
        units: Units of measure for this part (default='pcs')
        salable: Can this part be sold to customers?
        assembly: Can this part be build from other parts?
        component: Can this part be used to make other parts?
        purchaseable: Can this part be purchased from suppliers?
        trackable: Trackable parts can have unique serial numbers assigned, etc, etc
        active: Is this part active? Parts are deactivated instead of being deleted
        virtual: Is this part "virtual"? e.g. a software product or similar
        notes: Additional notes field for this part
        creation_date: Date that this part was added to the database
        creation_user: User who added this part to the database
        responsible: User who is responsible for this part (optional)
    """
    class Meta:
        verbose_name = "Part"
        verbose_name_plural = "Parts"

    def save(self, *args, **kwargs):
        """
        Overrides the save() function for the Part model.
        If the part image has been updated,
        then check if the "old" (previous) image is still used by another part.
        If not, it is considered "orphaned" and will be deleted.
        """

        if self.pk:
            previous = Part.objects.get(pk=self.pk)

            if previous.image and not self.image == previous.image:
                # Are there any (other) parts which reference the image?
                n_refs = Part.objects.filter(image=previous.image).exclude(
                    pk=self.pk).count()

                if n_refs == 0:
                    previous.image.delete(save=False)

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

    def __str__(self):
        return "{n} - {d}".format(n=self.full_name, d=self.description)

    @property
    def full_name(self):
        """ Format a 'full name' for this Part.

        - IPN (if not null)
        - Part name
        - Part variant (if not null)

        Elements are joined by the | character
        """

        elements = []

        if self.IPN:
            elements.append(self.IPN)

        elements.append(self.name)

        if self.revision:
            elements.append(self.revision)

        return ' | '.join(elements)

    def set_category(self, category):

        # Ignore if the category is already the same
        if self.category == category:
            return

        self.category = category
        self.save()

    def get_absolute_url(self):
        """ Return the web URL for viewing this part """
        return reverse('part-detail', kwargs={'pk': self.id})

    def get_image_url(self):
        """ Return the URL of the image for this part """

        if self.image:
            return helpers.getMediaUrl(self.image.url)
        else:
            return helpers.getBlankImage()

    def get_thumbnail_url(self):
        """
        Return the URL of the image thumbnail for this part
        """

        if self.image:
            return helpers.getMediaUrl(self.image.thumbnail.url)
        else:
            return helpers.getBlankThumbnail()

    def validate_unique(self, exclude=None):
        """ Validate that a part is 'unique'.
        Uniqueness is checked across the following (case insensitive) fields:

        * Name
        * IPN
        * Revision

        e.g. there can exist multiple parts with the same name, but only if
        they have a different revision or internal part number.

        """
        super().validate_unique(exclude)

        # Part name uniqueness should be case insensitive
        try:
            parts = Part.objects.exclude(id=self.id).filter(
                name__iexact=self.name,
                IPN__iexact=self.IPN,
                revision__iexact=self.revision)

            if parts.exists():
                msg = _("Part must be unique for name, IPN and revision")
                raise ValidationError({
                    "name": msg,
                    "IPN": msg,
                    "revision": msg,
                })
        except Part.DoesNotExist:
            pass

    def clean(self):
        """ Perform cleaning operations for the Part model """

        if self.is_template and self.variant_of is not None:
            raise ValidationError({
                'is_template':
                _("Part cannot be a template part if it is a variant of another part"
                  ),
                'variant_of':
                _("Part cannot be a variant of another part if it is already a template"
                  ),
            })

    name = models.CharField(max_length=100,
                            blank=False,
                            help_text=_('Part name'),
                            validators=[validators.validate_part_name])

    is_template = models.BooleanField(
        default=False, help_text=_('Is this part a template part?'))

    variant_of = models.ForeignKey(
        'part.Part',
        related_name='variants',
        null=True,
        blank=True,
        limit_choices_to={
            'is_template': True,
            'active': True,
        },
        on_delete=models.SET_NULL,
        help_text=_('Is this part a variant of another part?'))

    description = models.CharField(max_length=250,
                                   blank=False,
                                   help_text=_('Part description'))

    keywords = models.CharField(
        max_length=250,
        blank=True,
        help_text=_('Part keywords to improve visibility in search results'))

    category = TreeForeignKey(PartCategory,
                              related_name='parts',
                              null=True,
                              blank=True,
                              on_delete=models.DO_NOTHING,
                              help_text=_('Part category'))

    IPN = models.CharField(max_length=100,
                           blank=True,
                           help_text=_('Internal Part Number'),
                           validators=[validators.validate_part_ipn])

    revision = models.CharField(max_length=100,
                                blank=True,
                                help_text=_('Part revision or version number'))

    link = InvenTreeURLField(blank=True, help_text=_('Link to extenal URL'))

    image = StdImageField(
        upload_to=rename_part_image,
        null=True,
        blank=True,
        variations={'thumbnail': (128, 128)},
        delete_orphans=True,
    )

    default_location = TreeForeignKey(
        'stock.StockLocation',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        help_text=_('Where is this item normally stored?'),
        related_name='default_parts')

    def get_default_location(self):
        """ Get the default location for a Part (may be None).

        If the Part does not specify a default location,
        look at the Category this part is in.
        The PartCategory object may also specify a default stock location
        """

        if self.default_location:
            return self.default_location
        elif self.category:
            # Traverse up the category tree until we find a default location
            cats = self.category.get_ancestors(ascending=True,
                                               include_self=True)

            for cat in cats:
                if cat.default_location:
                    return cat.default_location

        # Default case - no default category found
        return None

    def get_default_supplier(self):
        """ Get the default supplier part for this part (may be None).

        - If the part specifies a default_supplier, return that
        - If there is only one supplier part available, return that
        - Else, return None
        """

        if self.default_supplier:
            return self.default_supplier

        if self.supplier_count == 1:
            return self.supplier_parts.first()

        # Default to None if there are multiple suppliers to choose from
        return None

    default_supplier = models.ForeignKey(SupplierPart,
                                         on_delete=models.SET_NULL,
                                         blank=True,
                                         null=True,
                                         help_text=_('Default supplier part'),
                                         related_name='default_parts')

    minimum_stock = models.PositiveIntegerField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text=_('Minimum allowed stock level'))

    units = models.CharField(max_length=20,
                             default="",
                             blank=True,
                             help_text=_('Stock keeping units for this part'))

    assembly = models.BooleanField(
        default=False,
        verbose_name='Assembly',
        help_text=_('Can this part be built from other parts?'))

    component = models.BooleanField(
        default=True,
        verbose_name='Component',
        help_text=_('Can this part be used to build other parts?'))

    trackable = models.BooleanField(
        default=False,
        help_text=_('Does this part have tracking for unique items?'))

    purchaseable = models.BooleanField(
        default=True,
        help_text=_('Can this part be purchased from external suppliers?'))

    salable = models.BooleanField(
        default=False, help_text=_("Can this part be sold to customers?"))

    active = models.BooleanField(default=True,
                                 help_text=_('Is this part active?'))

    virtual = models.BooleanField(
        default=False,
        help_text=_(
            'Is this a virtual part, such as a software product or license?'))

    notes = MarkdownxField(
        blank=True, help_text=_('Part notes - supports Markdown formatting'))

    bom_checksum = models.CharField(max_length=128,
                                    blank=True,
                                    help_text=_('Stored BOM checksum'))

    bom_checked_by = models.ForeignKey(User,
                                       on_delete=models.SET_NULL,
                                       blank=True,
                                       null=True,
                                       related_name='boms_checked')

    bom_checked_date = models.DateField(blank=True, null=True)

    creation_date = models.DateField(auto_now_add=True,
                                     editable=False,
                                     blank=True,
                                     null=True)

    creation_user = models.ForeignKey(User,
                                      on_delete=models.SET_NULL,
                                      blank=True,
                                      null=True,
                                      related_name='parts_created')

    responsible = models.ForeignKey(User,
                                    on_delete=models.SET_NULL,
                                    blank=True,
                                    null=True,
                                    related_name='parts_responible')

    def format_barcode(self):
        """ Return a JSON string for formatting a barcode for this Part object """

        return helpers.MakeBarcode(
            "part", {
                "id": self.id,
                "name": self.full_name,
                "url": reverse('api-part-detail', kwargs={'pk': self.id}),
            })

    @property
    def category_path(self):
        if self.category:
            return self.category.pathstring
        return ''

    @property
    def available_stock(self):
        """
        Return the total available stock.

        - This subtracts stock which is already allocated to builds
        """

        total = self.total_stock
        total -= self.allocation_count()

        return max(total, 0)

    @property
    def quantity_to_order(self):
        """ Return the quantity needing to be ordered for this part. """

        required = -1 * self.net_stock
        return max(required, 0)

    @property
    def net_stock(self):
        """ Return the 'net' stock. It takes into account:

        - Stock on hand (total_stock)
        - Stock on order (on_order)
        - Stock allocated (allocation_count)

        This number (unlike 'available_stock') can be negative.
        """

        return self.total_stock - self.allocation_count() + self.on_order

    def isStarredBy(self, user):
        """ Return True if this part has been starred by a particular user """

        try:
            PartStar.objects.get(part=self, user=user)
            return True
        except PartStar.DoesNotExist:
            return False

    def need_to_restock(self):
        """ Return True if this part needs to be restocked
        (either by purchasing or building).

        If the allocated_stock exceeds the total_stock,
        then we need to restock.
        """

        return (self.total_stock + self.on_order -
                self.allocation_count) < self.minimum_stock

    @property
    def can_build(self):
        """ Return the number of units that can be build with available stock
        """

        # If this part does NOT have a BOM, result is simply the currently available stock
        if not self.has_bom:
            return 0

        total = None

        # Calculate the minimum number of parts that can be built using each sub-part
        for item in self.bom_items.all().prefetch_related(
                'sub_part__stock_items'):
            stock = item.sub_part.available_stock
            n = int(stock / item.quantity)

            if total is None or n < total:
                total = n

        return max(total, 0)

    @property
    def active_builds(self):
        """ Return a list of outstanding builds.
        Builds marked as 'complete' or 'cancelled' are ignored
        """

        return self.builds.filter(status__in=BuildStatus.ACTIVE_CODES)

    @property
    def inactive_builds(self):
        """ Return a list of inactive builds
        """

        return self.builds.exclude(status__in=BuildStatus.ACTIVE_CODES)

    @property
    def quantity_being_built(self):
        """ Return the current number of parts currently being built
        """

        quantity = self.active_builds.aggregate(
            quantity=Sum('quantity'))['quantity']

        if quantity is None:
            quantity = 0

        return quantity

    def build_order_allocations(self):
        """
        Return all 'BuildItem' objects which allocate this part to Build objects
        """

        return BuildModels.BuildItem.objects.filter(
            stock_item__part__id=self.id)

    def build_order_allocation_count(self):
        """
        Return the total amount of this part allocated to build orders
        """

        query = self.build_order_allocations().aggregate(
            total=Coalesce(Sum('quantity'), 0))

        return query['total']

    def sales_order_allocations(self):
        """
        Return all sales-order-allocation objects which allocate this part to a SalesOrder
        """

        return OrderModels.SalesOrderAllocation.objects.filter(
            item__part__id=self.id)

    def sales_order_allocation_count(self):
        """
        Return the tutal quantity of this part allocated to sales orders
        """

        query = self.sales_order_allocations().aggregate(
            total=Coalesce(Sum('quantity'), 0))

        return query['total']

    def allocation_count(self):
        """
        Return the total quantity of stock allocated for this part,
        against both build orders and sales orders.
        """

        return sum([
            self.build_order_allocation_count(),
            self.sales_order_allocation_count(),
        ])

    @property
    def stock_entries(self):
        """ Return all 'in stock' items. To be in stock:

        - build_order is None
        - sales_order is None
        - belongs_to is None
        """

        return self.stock_items.filter(StockModels.StockItem.IN_STOCK_FILTER)

    @property
    def total_stock(self):
        """ Return the total stock quantity for this part.
        Part may be stored in multiple locations
        """

        if self.is_template:
            total = sum(
                [variant.total_stock for variant in self.variants.all()])
        else:
            total = self.stock_entries.filter(
                status__in=StockStatus.AVAILABLE_CODES).aggregate(
                    total=Sum('quantity'))['total']

        if total:
            return total
        else:
            return Decimal(0)

    @property
    def has_bom(self):
        return self.bom_count > 0

    @property
    def bom_count(self):
        """ Return the number of items contained in the BOM for this part """
        return self.bom_items.count()

    @property
    def used_in_count(self):
        """ Return the number of part BOMs that this part appears in """
        return self.used_in.count()

    def get_bom_hash(self):
        """ Return a checksum hash for the BOM for this part.
        Used to determine if the BOM has changed (and needs to be signed off!)

        The hash is calculated by hashing each line item in the BOM.

        returns a string representation of a hash object which can be compared with a stored value
        """

        hash = hashlib.md5(str(self.id).encode())

        for item in self.bom_items.all().prefetch_related('sub_part'):
            hash.update(str(item.get_item_hash()).encode())

        return str(hash.digest())

    @property
    def is_bom_valid(self):
        """ Check if the BOM is 'valid' - if the calculated checksum matches the stored value
        """

        return self.get_bom_hash() == self.bom_checksum

    @transaction.atomic
    def validate_bom(self, user):
        """ Validate the BOM (mark the BOM as validated by the given User.

        - Calculates and stores the hash for the BOM
        - Saves the current date and the checking user
        """

        # Validate each line item too
        for item in self.bom_items.all():
            item.validate_hash()

        self.bom_checksum = self.get_bom_hash()
        self.bom_checked_by = user
        self.bom_checked_date = datetime.now().date()

        self.save()

    @transaction.atomic
    def clear_bom(self):
        """ Clear the BOM items for the part (delete all BOM lines).
        """

        self.bom_items.all().delete()

    def required_parts(self):
        """ Return a list of parts required to make this part (list of BOM items) """
        parts = []
        for bom in self.bom_items.all().select_related('sub_part'):
            parts.append(bom.sub_part)
        return parts

    def get_allowed_bom_items(self):
        """ Return a list of parts which can be added to a BOM for this part.

        - Exclude parts which are not 'component' parts
        - Exclude parts which this part is in the BOM for
        """

        parts = Part.objects.filter(component=True).exclude(id=self.id)
        parts = parts.exclude(id__in=[part.id for part in self.used_in.all()])

        return parts

    @property
    def supplier_count(self):
        """ Return the number of supplier parts available for this part """
        return self.supplier_parts.count()

    @property
    def has_pricing_info(self):
        """ Return true if there is pricing information for this part """
        return self.get_price_range() is not None

    @property
    def has_complete_bom_pricing(self):
        """ Return true if there is pricing information for each item in the BOM. """

        for item in self.bom_items.all().select_related('sub_part'):
            if not item.sub_part.has_pricing_info:
                return False

        return True

    def get_price_info(self, quantity=1, buy=True, bom=True):
        """ Return a simplified pricing string for this part
        
        Args:
            quantity: Number of units to calculate price for
            buy: Include supplier pricing (default = True)
            bom: Include BOM pricing (default = True)
        """

        price_range = self.get_price_range(quantity, buy, bom)

        if price_range is None:
            return None

        min_price, max_price = price_range

        if min_price == max_price:
            return min_price

        min_price = normalize(min_price)
        max_price = normalize(max_price)

        return "{a} - {b}".format(a=min_price, b=max_price)

    def get_supplier_price_range(self, quantity=1):

        min_price = None
        max_price = None

        for supplier in self.supplier_parts.all():

            price = supplier.get_price(quantity)

            if price is None:
                continue

            if min_price is None or price < min_price:
                min_price = price

            if max_price is None or price > max_price:
                max_price = price

        if min_price is None or max_price is None:
            return None

        min_price = normalize(min_price)
        max_price = normalize(max_price)

        return (min_price, max_price)

    def get_bom_price_range(self, quantity=1):
        """ Return the price range of the BOM for this part.
        Adds the minimum price for all components in the BOM.

        Note: If the BOM contains items without pricing information,
        these items cannot be included in the BOM!
        """

        min_price = None
        max_price = None

        for item in self.bom_items.all().select_related('sub_part'):

            if item.sub_part.pk == self.pk:
                print("Warning: Item contains itself in BOM")
                continue

            prices = item.sub_part.get_price_range(quantity * item.quantity)

            if prices is None:
                continue

            low, high = prices

            if min_price is None:
                min_price = 0

            if max_price is None:
                max_price = 0

            min_price += low
            max_price += high

        if min_price is None or max_price is None:
            return None

        min_price = normalize(min_price)
        max_price = normalize(max_price)

        return (min_price, max_price)

    def get_price_range(self, quantity=1, buy=True, bom=True):
        """ Return the price range for this part. This price can be either:

        - Supplier price (if purchased from suppliers)
        - BOM price (if built from other parts)

        Returns:
            Minimum of the supplier price or BOM price. If no pricing available, returns None
        """

        buy_price_range = self.get_supplier_price_range(
            quantity) if buy else None
        bom_price_range = self.get_bom_price_range(quantity) if bom else None

        if buy_price_range is None:
            return bom_price_range

        elif bom_price_range is None:
            return buy_price_range

        else:
            return (min(buy_price_range[0], bom_price_range[0]),
                    max(buy_price_range[1], bom_price_range[1]))

    def deepCopy(self, other, **kwargs):
        """ Duplicates non-field data from another part.
        Does not alter the normal fields of this part,
        but can be used to copy other data linked by ForeignKey refernce.

        Keyword Args:
            image: If True, copies Part image (default = True)
            bom: If True, copies BOM data (default = False)
        """

        # Copy the part image
        if kwargs.get('image', True):
            if other.image:
                # Reference the other image from this Part
                self.image = other.image

        # Copy the BOM data
        if kwargs.get('bom', False):
            for item in other.bom_items.all():
                # Point the item to THIS part.
                # Set the pk to None so a new entry is created.
                item.part = self
                item.pk = None
                item.save()

        # Copy the fields that aren't available in the duplicate form
        self.salable = other.salable
        self.assembly = other.assembly
        self.component = other.component
        self.purchaseable = other.purchaseable
        self.trackable = other.trackable
        self.virtual = other.virtual

        self.save()

    @property
    def attachment_count(self):
        """ Count the number of attachments for this part.
        If the part is a variant of a template part,
        include the number of attachments for the template part.

        """

        n = self.attachments.count()

        if self.variant_of:
            n += self.variant_of.attachments.count()

        return n

    def sales_orders(self):
        """ Return a list of sales orders which reference this part """

        orders = []

        for line in self.sales_order_line_items.all().prefetch_related(
                'order'):
            if line.order not in orders:
                orders.append(line.order)

        return orders

    def purchase_orders(self):
        """ Return a list of purchase orders which reference this part """

        orders = []

        for part in self.supplier_parts.all().prefetch_related(
                'purchase_order_line_items'):
            for order in part.purchase_orders():
                if order not in orders:
                    orders.append(order)

        return orders

    def open_purchase_orders(self):
        """ Return a list of open purchase orders against this part """

        return [
            order for order in self.purchase_orders()
            if order.status in PurchaseOrderStatus.OPEN
        ]

    def closed_purchase_orders(self):
        """ Return a list of closed purchase orders against this part """

        return [
            order for order in self.purchase_orders()
            if order.status not in PurchaseOrderStatus.OPEN
        ]

    @property
    def on_order(self):
        """ Return the total number of items on order for this part. """

        orders = self.supplier_parts.filter(
            purchase_order_line_items__order__status__in=PurchaseOrderStatus.
            OPEN).aggregate(
                quantity=Sum('purchase_order_line_items__quantity'),
                received=Sum('purchase_order_line_items__received'))

        quantity = orders['quantity']
        received = orders['received']

        if quantity is None:
            quantity = 0

        if received is None:
            received = 0

        return quantity - received

    def get_parameters(self):
        """ Return all parameters for this part, ordered by name """

        return self.parameters.order_by('template__name')
Esempio n. 5
0
class Storage(models.Model):
    STORAGE_STATE = (
        ('R', 'Requested'),
        ('AG', 'Auto granted (<= month)'),
        ('1st', 'First extension'),
        ('OK', 'Approved'),
        ('NO', 'Denied'),
        ('EX', 'Expired'),
        ('D', 'Rescinded'),
    )
    image = StdImageField(upload_to=upload_to_pattern,
                          blank=True,
                          null=True,
                          delete_orphans=True,
                          variations=settings.IMG_VARIATIONS,
                          validators=settings.IMG_VALIDATORS)

    what = models.CharField(max_length=200)
    location = models.CharField(max_length=50)

    extra_info = models.TextField(max_length=1000)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)

    requested = models.DateField(auto_now_add=True)
    duration = models.IntegerField(default=30, help_text='days')

    state = models.CharField(max_length=4, choices=STORAGE_STATE)

    extends = models.OneToOneField('self',
                                   on_delete=models.CASCADE,
                                   blank=True,
                                   null=True)

    # Auto calculated
    lastdate = models.DateField(blank=True, null=True)
    history = HistoricalRecords()

    def enddate(self):
        return self.requested + datetime.timedelta(days=self.duration)

    def expired(self):
        return self.enddate() < datetime.date.today()

    def location_updatable(self):  # we can update the location
        return self.state in ('AG', 'OK', 'R', '1st', 'AG')

    def justification_updatable(self):  # we can update the justification
        return self.state in ('R', '1st')

    def extendable(self):  # we can ask for an extention (anew)
        return self.state in ('OK', 'AG')

    def editable(self):  # we can still change anything
        return self.state in ('R')

    def deletable(self):
        return self.state in ('R', 'OK', '1st', 'AG')

    def __str__(self):
        return self.what + "@" + self.location

    class AgainstTheRules(Exception):
        pass

    def path(self):
        return reverse('showstorage', kwargs={'pk': self.id})

    def history_path(self):
        return reverse('showhistory', kwargs={'pk': self.id})

    def owner_path(self):
        return reverse('storage', kwargs={'user_pk': self.owner.id})

    def url(self):
        return settings.BASE + self.path()

    def history_url(self):
        return settings.BASE + self.history_path()

    def owner_url(self):
        return settings.BASE + self.owner_path()

    def file_for_extension(self):
        if not self.extendable:
            raise AgainstTheRules('Cannot file an extension on ' + self.state +
                                  ', needs to be OK/AG')

        n = Storage(self)
        n.state = '1st'
        n.extends = self
        n.save()

    def apply_rules(self, user=None):
        s = self.state

        if self.state == '':
            self._history_user = None
            self.changeReason = '[rule] State set to R'
            self.state = 'R'

        if self.state == 'R' and self.duration <= 31:
            self._history_user = None
            self.changeReason = '[rule] Auto approved (month or less)'
            self.state = 'AG'

        if self.expired():
            self._history_user = None
            self.changeReason = '[rule] Expired -- been there too long'
            self.state = 'EX'

        if s != self.state:
            self.save()

        return
Esempio n. 6
0
class Course(TimeStampedModel, ChangedByMixin):
    """ Publisher Course model. It contains fields related to the course intake form."""

    # Versions for code paths in publisher Course and Course Run Create/Edit
    # Is the original version for courses without Entitlements (No mode/price set at Course level)
    SEAT_VERSION = 0
    # Is the version for Courses that have a mode and price set (a CourseEntitlement), where all course runs must match
    ENTITLEMENT_VERSION = 1

    title = models.CharField(max_length=255,
                             default=None,
                             null=True,
                             blank=True,
                             verbose_name=_('Course title'))
    number = models.CharField(max_length=50,
                              null=True,
                              blank=True,
                              verbose_name=_('Course number'))
    short_description = models.TextField(default=None,
                                         null=True,
                                         blank=True,
                                         verbose_name=_('Brief Description'))
    full_description = models.TextField(default=None,
                                        null=True,
                                        blank=True,
                                        verbose_name=_('Full Description'))
    organizations = models.ManyToManyField(Organization,
                                           blank=True,
                                           related_name='publisher_courses',
                                           verbose_name=_('Partner Name'))
    level_type = models.ForeignKey(
        LevelType,
        models.CASCADE,
        default=None,
        null=True,
        blank=True,
        related_name='publisher_courses',
        verbose_name=_('Level Type'),
    )
    expected_learnings = models.TextField(default=None,
                                          null=True,
                                          blank=True,
                                          verbose_name=_("Expected Learnings"))
    syllabus = models.TextField(default=None, null=True, blank=True)
    prerequisites = models.TextField(default=None,
                                     null=True,
                                     blank=True,
                                     verbose_name=_('Prerequisites'))
    learner_testimonial = models.TextField(
        default=None,
        null=True,
        blank=True,
        verbose_name=_('Learner Testimonials'))
    additional_information = models.TextField(
        default=None,
        null=True,
        blank=True,
        verbose_name=_('Additional Information'))
    primary_subject = models.ForeignKey(
        Subject,
        models.CASCADE,
        default=None,
        null=True,
        blank=True,
        related_name='publisher_courses_primary',
    )
    secondary_subject = models.ForeignKey(
        Subject,
        models.CASCADE,
        default=None,
        null=True,
        blank=True,
        related_name='publisher_courses_secondary',
    )
    tertiary_subject = models.ForeignKey(
        Subject,
        models.CASCADE,
        default=None,
        null=True,
        blank=True,
        related_name='publisher_courses_tertiary',
    )

    image = StdImageField(
        upload_to=UploadToFieldNamePath(populate_from='number',
                                        path='media/publisher/courses/images'),
        blank=True,
        null=True,
        validators=[
            ImageMultiSizeValidator([(2120, 1192), (378, 225)],
                                    preferred_size=(1134, 675))
        ])

    is_seo_review = models.BooleanField(default=False)
    keywords = TaggableManager(blank=True, verbose_name='keywords')
    faq = models.TextField(default=None,
                           null=True,
                           blank=True,
                           verbose_name=_('FAQ'))
    video_link = models.URLField(default=None,
                                 null=True,
                                 blank=True,
                                 verbose_name=_('Video Link'))
    version = models.IntegerField(default=SEAT_VERSION,
                                  verbose_name='Workflow Version')

    # temp fields for data migrations only.
    course_metadata_pk = models.PositiveIntegerField(
        null=True, blank=True, verbose_name=_('Course Metadata Course PK'))
    url_slug = AutoSlugField(
        populate_from='title',
        editable=True,
        slugify_function=uslugify,
        overwrite_on_add=False,
        help_text=_(
            'Leave this field blank to have the value generated automatically.'
        ),
        unique=True)

    history = HistoricalRecords(excluded_fields=['url_slug'])

    def __str__(self):
        return self.title

    @property
    def uses_entitlements(self):
        """
        Returns a bool indicating whether or not this Course has been configured to use entitlement products.
        """
        return self.version == self.ENTITLEMENT_VERSION

    @property
    def post_back_url(self):
        return reverse('publisher:publisher_courses_edit',
                       kwargs={'pk': self.id})

    class Meta(TimeStampedModel.Meta):
        permissions = (('view_course', 'Can view course'), )

    def get_course_users_emails(self):
        """ Returns the list of users emails with enable email notifications
        against a course. By default if attribute value does not exists
        then user will be eligible for emails.
        """
        user_emails = [
            role.user.email for role in self.course_user_roles.all()
            if is_email_notification_enabled(role.user)
        ]

        return user_emails

    def get_user_role(self, user):
        """
        Returns the role of a user in the course if it exists
        """
        try:
            return self.course_user_roles.get(user=user).role
        except CourseUserRole.DoesNotExist:
            return None

    @property
    def organization_name(self):
        """
        Returns organization name for a course.
        """
        organization_name = ''
        try:
            organization_name = self.organizations.only('key').first().key
        except AttributeError:
            pass

        return organization_name

    @property
    def keywords_data(self):
        keywords = self.keywords.all()
        if keywords:
            return ', '.join(k.name for k in keywords)

        return None

    @property
    def project_coordinator(self):
        """
        Return course project coordinator user.
        """
        try:
            return self.course_user_roles.only('user').get(
                role=PublisherUserRole.ProjectCoordinator).user
        except CourseUserRole.DoesNotExist:
            return None

    def assign_organization_role(self, organization):
        """
        Create course-user-roles except CourseTeam for the given organization against a course.
        """
        for user_role in organization.organization_user_roles.exclude(
                role=PublisherUserRole.CourseTeam):
            CourseUserRole.add_course_roles(self, user_role.role,
                                            user_role.user)

    @property
    def course_runs(self):
        return self.publisher_course_runs.order_by('-created')

    @property
    def course_team_admin(self):
        """
        Return course team user.
        """
        try:
            return self.course_user_roles.get(
                role=PublisherUserRole.CourseTeam).user
        except CourseUserRole.DoesNotExist:
            return None

    @property
    def partner(self):
        organization = self.organizations.all().first()
        return organization.partner if organization else None

    @property
    def marketing_reviewer(self):
        """
        Return course marketing reviewer user.
        """
        try:
            return self.course_user_roles.get(
                role=PublisherUserRole.MarketingReviewer).user
        except CourseUserRole.DoesNotExist:
            return None

    @property
    def organization_extension(self):
        organization = self.organizations.all().first()
        if organization:
            return organization.organization_extension

        return None

    @property
    def publisher(self):
        """
        Return course publisher user.
        """
        try:
            return self.course_user_roles.get(
                role=PublisherUserRole.Publisher).user
        except CourseUserRole.DoesNotExist:
            return None

    @property
    def course_short_description(self):
        course_run = self.course_runs.filter(
            course_run_state__name=CourseRunStateChoices.Published).first()

        if course_run and course_run.short_description_override:
            return course_run.short_description_override

        return self.short_description

    @property
    def course_full_description(self):
        course_run = self.course_runs.filter(
            course_run_state__name=CourseRunStateChoices.Published).first()

        if course_run and course_run.full_description_override:
            return course_run.full_description_override

        return self.full_description

    @property
    def course_title(self):
        course_run = self.course_runs.filter(
            course_run_state__name=CourseRunStateChoices.Published).first()

        if course_run and course_run.title_override:
            return course_run.title_override

        return self.title

    @cached_property
    def discovery_counterpart(self):
        return DiscoveryCourse.objects.get(partner=self.partner, key=self.key)

    @cached_property
    def key(self):
        return '{org}+{number}'.format(org=self.organizations.first().key,
                                       number=self.number)
Esempio n. 7
0
class Event(BaseAPIResource, NationBuilderResource, LocationMixin, ImageMixin,
            DescriptionMixin, ContactMixin):
    """
    Model that represents an event
    """
    objects = EventQuerySet.as_manager()

    name = models.CharField(
        _("nom"),
        max_length=255,
        blank=False,
        help_text=_("Le nom de l'événement"),
    )

    published = models.BooleanField(
        _('publié'),
        default=True,
        help_text=_('L\'évenement doit-il être visible publiquement.'))

    nb_path = models.CharField(_('NationBuilder path'),
                               max_length=255,
                               blank=True)

    tags = models.ManyToManyField('EventTag',
                                  related_name='events',
                                  blank=True)

    start_time = CustomDateTimeField(_('date et heure de début'), blank=False)
    end_time = CustomDateTimeField(_('date et heure de fin'), blank=False)

    calendar = models.ForeignKey('Calendar',
                                 related_name='events',
                                 blank=False,
                                 on_delete=models.PROTECT)

    attendees = models.ManyToManyField('people.Person',
                                       related_name='events',
                                       through='RSVP')

    organizers = models.ManyToManyField('people.Person',
                                        related_name='organized_events',
                                        through="OrganizerConfig")
    organizers_groups = models.ManyToManyField('groups.SupportGroup',
                                               related_name='organized_events',
                                               through="OrganizerConfig")

    report_image = StdImageField(
        verbose_name=_('image de couverture'),
        blank=True,
        variations={
            'thumbnail': (400, 250),
            'banner': (1200, 400),
        },
        upload_to=UploadToInstanceDirectoryWithFilename('report_banner'),
        help_text=_(
            "Cette image apparaîtra en tête de votre compte-rendu, et dans les partages que vous ferez du"
            " compte-rendu sur les réseaux sociaux."),
    )

    report_content = DescriptionField(
        verbose_name=_("compte-rendu de l'événement"),
        blank=True,
        allowed_tags='allowed_tags',
        help_text=
        _("Ajoutez un compte-rendu de votre événement. N'hésitez pas à inclure des photos."
          ))

    class Meta:
        verbose_name = _('événement')
        verbose_name_plural = _('événements')
        ordering = ('-start_time', '-end_time')
        permissions = (
            # DEPRECIATED: every_event was set up as a potential solution to Rest Framework django permissions
            # Permission class default behaviour of requiring both global permissions and object permissions before
            # allowing users. Was not used in the end.s
            ('every_event', _('Peut éditer tous les événements')),
            ('view_hidden_event', _('Peut voir les événements non publiés')),
        )
        indexes = (
            models.Index(fields=['start_time', 'end_time'],
                         name='events_datetime_index'),
            models.Index(fields=['end_time'], name='events_end_time_index'),
            models.Index(fields=['nb_path'], name='events_nb_path_index'),
        )

    def __str__(self):
        return self.name

    @property
    def participants(self):
        try:
            return self._participants
        except AttributeError:
            return self.rsvps.aggregate(
                participants=models.Sum(models.F('guests') +
                                        1))['participants']

    def get_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)
        end_time = self.end_time.astimezone(tz)

        if start_time.date() == end_time.date():
            date = formats.date_format(start_time, 'DATE_FORMAT')
            return _("le {date}, de {start_hour} à {end_hour}").format(
                date=date,
                start_hour=formats.time_format(start_time, 'TIME_FORMAT'),
                end_hour=formats.time_format(end_time, 'TIME_FORMAT'))

        return _(
            "du {start_date}, {start_time} au {end_date}, {end_time}").format(
                start_date=formats.date_format(start_time, 'DATE_FORMAT'),
                start_time=formats.date_format(start_time, 'TIME_FORMAT'),
                end_date=formats.date_format(end_time, 'DATE_FORMAT'),
                end_time=formats.date_format(end_time, 'TIME_FORMAT'),
            )

    def is_past(self):
        return timezone.now() > self.end_time

    def clean(self):
        if self.start_time and self.end_time and self.end_time < self.start_time:
            raise ValidationError({
                'end_time':
                _("La date de fin de l'événement doit être postérieure à sa date de début."
                  )
            })
Esempio n. 8
0
class Event(
        ExportModelOperationsMixin("event"),
        BaseAPIResource,
        LocationMixin,
        ImageMixin,
        DescriptionMixin,
        ContactMixin,
):
    """
    Model that represents an event
    """

    objects = EventQuerySet.as_manager()

    name = models.CharField(_("nom"),
                            max_length=255,
                            blank=False,
                            help_text=_("Le nom de l'événement"))

    VISIBILITY_ADMIN = "A"
    VISIBILITY_ORGANIZER = "O"
    VISIBILITY_PUBLIC = "P"
    VISIBILITY_CHOICES = (
        (VISIBILITY_ADMIN, "Caché"),
        (VISIBILITY_ORGANIZER, "Visible par les organisateurs"),
        (VISIBILITY_PUBLIC, "Public"),
    )

    visibility = models.CharField(
        "Visibilité",
        max_length=1,
        choices=VISIBILITY_CHOICES,
        default=VISIBILITY_PUBLIC,
    )

    subtype = models.ForeignKey(
        "EventSubtype",
        verbose_name="Sous-type",
        related_name="events",
        on_delete=models.PROTECT,
        default=get_default_subtype,
    )

    tags = models.ManyToManyField("EventTag",
                                  related_name="events",
                                  blank=True)

    start_time = CustomDateTimeField(_("date et heure de début"), blank=False)
    end_time = CustomDateTimeField(_("date et heure de fin"), blank=False)
    max_participants = models.IntegerField("Nombre maximum de participants",
                                           blank=True,
                                           null=True)
    allow_guests = models.BooleanField(
        "Autoriser les participant⋅e⋅s à inscrire des invité⋅e⋅s",
        default=False)
    facebook = FacebookEventField("Événement correspondant sur Facebook",
                                  blank=True)

    attendees = models.ManyToManyField("people.Person",
                                       related_name="events",
                                       through="RSVP")

    organizers = models.ManyToManyField("people.Person",
                                        related_name="organized_events",
                                        through="OrganizerConfig")
    organizers_groups = models.ManyToManyField(
        "groups.SupportGroup",
        related_name="organized_events",
        through="OrganizerConfig",
    )

    report_image = StdImageField(
        verbose_name=_("image de couverture"),
        blank=True,
        variations={
            "thumbnail": (400, 250),
            "banner": (1200, 400)
        },
        upload_to=report_image_path,
        help_text=_(
            "Cette image apparaîtra en tête de votre compte-rendu, et dans les partages que vous ferez du"
            " compte-rendu sur les réseaux sociaux."),
    )

    report_content = DescriptionField(
        verbose_name=_("compte-rendu de l'événement"),
        blank=True,
        allowed_tags="allowed_tags",
        help_text=
        _("Ajoutez un compte-rendu de votre événement. N'hésitez pas à inclure des photos."
          ),
    )

    report_summary_sent = models.BooleanField(
        "Le mail de compte-rendu a été envoyé", default=False)

    subscription_form = models.OneToOneField("people.PersonForm",
                                             null=True,
                                             blank=True,
                                             on_delete=models.PROTECT)
    payment_parameters = JSONField(verbose_name=_("Paramètres de paiement"),
                                   null=True,
                                   blank=True)

    scanner_event = models.IntegerField(
        "L'ID de l'événement sur le logiciel de tickets",
        blank=True,
        null=True)
    scanner_category = models.IntegerField(
        "La catégorie que doivent avoir les tickets sur scanner",
        blank=True,
        null=True)

    enable_jitsi = models.BooleanField("Activer la visio-conférence",
                                       default=False)

    participation_template = models.TextField(
        _("Template pour la page de participation"), blank=True, null=True)

    do_not_list = models.BooleanField(
        "Ne pas lister l'événement",
        default=False,
        help_text=
        "L'événement n'apparaîtra pas sur la carte, ni sur le calendrier "
        "et ne sera pas cherchable via la recherche interne ou les moteurs de recherche.",
    )

    legal = JSONField(
        _("Informations juridiques"),
        default=dict,
        blank=True,
        encoder=CustomJSONEncoder,
    )

    FOR_USERS_INSOUMIS = "I"
    FOR_USERS_2022 = "2"
    FOR_USERS_CHOICES = (
        (FOR_USERS_INSOUMIS, "Les insoumis⋅es"),
        (FOR_USERS_2022, "Les signataires « Nous Sommes Pour ! »"),
    )

    for_users = models.CharField(
        "Utilisateur⋅ices de la plateforme concerné⋅es par l'événement",
        default=FOR_USERS_INSOUMIS,
        max_length=1,
        blank=False,
        choices=FOR_USERS_CHOICES,
    )

    class Meta:
        verbose_name = _("événement")
        verbose_name_plural = _("événements")
        ordering = ("-start_time", "-end_time")
        permissions = (
            # DEPRECIATED: every_event was set up as a potential solution to Rest Framework django permissions
            # Permission class default behaviour of requiring both global permissions and object permissions before
            # allowing users. Was not used in the end.s
            ("every_event", _("Peut éditer tous les événements")),
            ("view_hidden_event", _("Peut voir les événements non publiés")),
        )
        indexes = (
            models.Index(fields=["start_time", "end_time"],
                         name="events_datetime_index"),
            models.Index(fields=["end_time"], name="events_end_time_index"),
        )

    def __str__(self):
        return f"{self.name} ({self.get_display_date()})"

    def __repr__(self):
        return f"{self.__class__.__name__}(id={str(self.pk)!r}, name={self.name!r})"

    def to_ics(self):
        event_url = front_url("view_event", args=[self.pk], auto_login=False)
        return ics.Event(
            name=self.name,
            begin=self.start_time,
            end=self.end_time,
            uid=str(self.pk),
            description=self.description + f"<p>{event_url}</p>",
            location=self.short_address,
            url=event_url,
        )

    @property
    def participants(self):
        try:
            return self.all_attendee_count
        except AttributeError:
            if self.subscription_form:
                return (self.rsvps.annotate(identified_guests_count=Count(
                    "identified_guests")).aggregate(
                        participants=Sum(F("identified_guests_count") +
                                         1))["participants"] or 0)
            return (self.rsvps.aggregate(
                participants=Sum(models.F("guests") + 1))["participants"] or 0)

    @property
    def type(self):
        return self.subtype.type

    def get_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)
        end_time = self.end_time.astimezone(tz)

        if start_time.date() == end_time.date():
            date = formats.date_format(start_time, "DATE_FORMAT")
            return _("le {date}, de {start_hour} à {end_hour}").format(
                date=date,
                start_hour=formats.time_format(start_time, "TIME_FORMAT"),
                end_hour=formats.time_format(end_time, "TIME_FORMAT"),
            )

        return _(
            "du {start_date}, {start_time} au {end_date}, {end_time}").format(
                start_date=formats.date_format(start_time, "DATE_FORMAT"),
                start_time=formats.date_format(start_time, "TIME_FORMAT"),
                end_date=formats.date_format(end_time, "DATE_FORMAT"),
                end_time=formats.date_format(end_time, "TIME_FORMAT"),
            )

    def get_simple_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)

        return _("le {date} à {time}").format(
            date=formats.date_format(start_time, "DATE_FORMAT"),
            time=formats.time_format(start_time, "TIME_FORMAT"),
        )

    def is_past(self):
        return timezone.now() > self.end_time

    def is_current(self):
        return self.start_time < timezone.now() < self.end_time

    def clean(self):
        if self.start_time and self.end_time and self.end_time < self.start_time:
            raise ValidationError({
                "end_time":
                _("La date de fin de l'événement doit être postérieure à sa date de début."
                  )
            })

    def get_price_display(self):
        if self.payment_parameters is None:
            return None

        base_price = self.payment_parameters.get("price", 0)
        min_price = base_price
        max_price = base_price

        for mapping in self.payment_parameters.get("mappings", []):
            prices = [m["price"] for m in mapping["mapping"]]
            min_price += min(prices)
            max_price += max(prices)

        if min_price == max_price == 0:
            if "free_pricing" in self.payment_parameters:
                return "Prix libre"
            else:
                return None

        if min_price == max_price:
            display = "{} €".format(floatformat(min_price / 100, 2))
        else:
            display = "de {} à {} €".format(floatformat(min_price / 100, 2),
                                            floatformat(max_price / 100, 2))

        if "free_pricing" in self.payment_parameters:
            display += " + montant libre"

        return display

    @property
    def is_free(self):
        return self.payment_parameters is None

    @property
    def is_2022(self):
        return self.for_users == self.FOR_USERS_2022

    def get_price(self, submission_data: dict = None):
        price = self.payment_parameters.get("price", 0)

        if submission_data is None:
            return price

        for mapping in self.payment_parameters.get("mappings", []):
            values = [
                submission_data.get(field) for field in mapping["fields"]
            ]

            d = {
                tuple(v for v in m["values"]): m["price"]
                for m in mapping["mapping"]
            }

            price += d[tuple(values)]

        if "free_pricing" in self.payment_parameters:
            field = self.payment_parameters["free_pricing"]
            price += max(0, int(submission_data.get(field, 0) * 100))

        return price

    def get_absolute_url(self):
        return front_url("view_event", args=[self.pk])

    def get_google_calendar_url(self):
        df = "%Y%m%dT%H%i00"

        query = {
            "text": self.name,
            "dates":
            f"{self.start_time.strftime(df)}/{self.end_time.strftime(df)}",
            "location": self.short_address,
            "details": self.description,
        }

        return f"https://calendar.google.com/calendar/r/eventedit?{urlencode(query)}"

    def can_rsvp(self, person):
        return (self.for_users == Event.FOR_USERS_2022 and person.is_2022) or (
            self.for_users == Event.FOR_USERS_INSOUMIS and person.is_insoumise)
Esempio n. 9
0
class Person(models.Model):
    public_id = models.CharField(
        primary_key=True,
        default=peep_public_id,
        help_text="A unique identifier for this person that is safe to share publicly.",
        max_length=24,
        editable=False,
    )

    first_name = models.CharField(max_length=255, blank=False, help_text="First Name")

    last_name = models.CharField(max_length=255, blank=False, help_text="Last Name")

    # xxx May want to restrict input on this in the future
    role = models.CharField(max_length=255, blank=False, help_text="Job Role")

    email = models.CharField(max_length=255, blank=False, help_text="Email")

    office_phone_country_code = models.CharField(max_length=5, blank=True, help_text="Office phone country code")

    office_phone = models.CharField(
        max_length=255, blank=True, help_text="Office Phone"
    )

    office_phone_ext = models.CharField(max_length=255, blank=True, help_text="Phone extension")

    # cell_phone = models.CharField(max_length=255, blank=True, help_text="Cell Phone")

    office = models.ForeignKey(
        "crm.Office",
        on_delete=models.CASCADE,
        null=True,
        blank=False,
        help_text="Office the person works at",
    )

    user = models.OneToOneField(
        "users.User",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text="User associated with this person",
    )

    avatar = StdImageField(
        null=True,
        blank=True,
        default="",
        upload_to=avatar_media_path,
        help_text="""A full-resolution user avatar.<br/>Resized variants (100x100, 36x36) will also be created on Amazon S3.""",
        variations={"regular": (100, 100, True), "thumbnail": (36, 36, True)},
    )

    objects = PeopleManager()

    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"

    def __str__(self):
        if self.office:
            s = "{}: {} {} ({})".format(
                self.office.business.name, self.first_name, self.last_name, self.public_id
            )
        else:
            s = "{} {} ({})".format(
                self.first_name, self.last_name, self.public_id
            )
        return s
Esempio n. 10
0
class Universidad(models.Model):
    PUBLICA = 0
    PRIVADA = 1

    def __init__(self, *args, **kwargs):
        self.get_siglas_no_centro = self._get_siglas_no_centro
        super(Universidad, self).__init__(*args, **kwargs)

    TIPO_UNIVERSIDAD_CHOICES = ((PUBLICA, 'Pública'), (PRIVADA, 'Privada'))

    siglas = models.CharField(
        max_length=20,
        unique=True,
        null=False,
        blank=False,
        validators=[
            RegexValidator(
                regex=r'[A-Za-z\-]+',
                message=ugettext_lazy(
                    "Las siglas de la universidad solo pueden contener letras y guiones (-)"
                ))
        ],
        help_text=ugettext_lazy("Siglas de la universidad"))
    nombre = models.CharField(
        max_length=200,
        null=False,
        blank=False,
        help_text=ugettext_lazy("Nombre de la universidad"))
    tipo = models.IntegerField(
        choices=TIPO_UNIVERSIDAD_CHOICES,
        null=False,
        blank=False,
        help_text=ugettext_lazy("Tipo de centro (público/privado)"))
    centro = models.CharField(max_length=200,
                              null=True,
                              blank=True,
                              help_text=ugettext_lazy("Nombre del centro"))
    provincia = models.CharField(max_length=50,
                                 choices=provincias,
                                 blank=False,
                                 null=False,
                                 help_text=ugettext_lazy("Provincia"))
    logo = StdImageField(upload_to=settings.ESCUDOS_PATH,
                         null=True,
                         blank=True,
                         variations={'thumbnail': (100, 100, True)},
                         validators=[MinSizeValidator(100, 100)],
                         help_text=ugettext_lazy("Escudo de la universidad"))

    campus = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text=ugettext_lazy(
            "Nombre del campus"))  # TODO: Hacer obligatorio?
    url = models.URLField(max_length=300,
                          null=True,
                          blank=True,
                          help_text=ugettext_lazy("URL del centro"))

    def _get_siglas_no_centro(self):
        return self.get_siglas_no_centro(self.siglas)

    @staticmethod
    def get_siglas_no_centro(siglas):
        """
        Elimina el sufijo del centro, útil para procesar imágenes
        Returns:

        """
        return re.sub(r'\-.*', '', siglas)

    def get_provincia_unicode(self):
        return dict(provincias).get(self.provincia)

    @property
    def tipo_universidad_verbose(self):
        return self.get_tipo_universidad_verbose(self.tipo)

    @classmethod
    def get_tipo_universidad_verbose(cls, tipo_universidad):
        #self
        return str(
            dict((tipo, nombre) for tipo, nombre in
                 cls.TIPO_UNIVERSIDAD_CHOICES).get(tipo_universidad))

    def __str__(self):
        return self.nombre
Esempio n. 11
0
class Post(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_DRAFT = 2
    STATUS_ITEMS = (
        (STATUS_NORMAL, '正常'),
        (STATUS_DELETE, '删除'),
        (STATUS_DRAFT, '草稿'),
    )
    image1 = StdImageField(
        default='goods/319104.jpg',
        upload_to="goods/%Y/%m",
        verbose_name=u"图片",
        variations={'thumbnail': {
            'width': 100,
            'height': 75
        }},
    )
    pv = models.PositiveIntegerField(default=1)
    uv = models.PositiveIntegerField(default=1)
    content_html = models.TextField(verbose_name="正文html代码",
                                    blank=True,
                                    editable=False)
    title = models.CharField(max_length=255, verbose_name="标题")
    desc = models.CharField(max_length=1024, blank=True, verbose_name="摘要")
    content = models.TextField(verbose_name="正文", help_text="正文必须为MarkDown格式")
    status = models.PositiveIntegerField(default=STATUS_NORMAL,
                                         choices=STATUS_ITEMS,
                                         verbose_name="状态")
    category = models.ForeignKey(Category,
                                 verbose_name="分类",
                                 on_delete=models.CASCADE)
    tag = models.ManyToManyField(Tag, verbose_name="标签")
    owner = models.ForeignKey(User,
                              verbose_name="作者",
                              on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    is_md = models.BooleanField(default=False, verbose_name="markdown语法")

    class Meta:
        verbose_name = verbose_name_plural = '文章'
        ordering = ['-id']  #根据id进行降序排列

    def image_img1(self):
        if self.image1:
            return str('<img src="%s" />' % self.image1.thumbnail.url)
        else:
            return '上传图片'

    image_img1.short_description = '图片'
    image_img1.allow_tags = True  # 列表页显示图片

    def __str__(self):
        return self.title

    #获取最新文章数据的操作
    @staticmethod
    def get_by_tag(tag_id):
        try:
            tag = Tag.objects.get(id=tag_id)
        except Tag.DoesNotExist:
            tag = None
            post_list = []
        else:
            post_list = tag.post_set.filter(
                status=Post.STATUS_NORMAL).select_related('owner', 'category')
        return post_list, tag

    @staticmethod
    def get_by_category(category_id):
        try:
            category = Category.objects.get(id=category_id)
        except Category.DoesNotExist:
            category = None
            post_list = []
        else:
            post_list = category.post_set.filter(
                status=Post.STATUS_NORMAL).select_related('owner', 'category')
        return post_list, category

    @cached_property
    def tags(self):
        return ','.join(self.tag.values_list('name', flat=True))

    @classmethod
    def latest_posts(cls, with_related=True):
        queryset = cls.objects.filter(status=cls.STATUS_NORMAL)
        if with_related:
            queryset = queryset.select_related('owner', 'category')
        return queryset[0:5]

    #热门文章
    @classmethod
    def hot_posts(cls):
        result = cache.get('hot_posts')
        if not result:
            result = cls.objects.filter(
                status=cls.STATUS_NORMAL).order_by('-pv')
            cache.set('hot_posts', result, 10 * 60)
        return result[0:5]

    def save(self, *args, **kwargs):
        if self.is_md:
            self.content_html = mistune.markdown(self.content)
        else:
            self.content_html = self.content
        super().save(*args, **kwargs)
Esempio n. 12
0
class Calendar(ImageMixin):
    objects = CalendarManager()

    name = models.CharField(_("titre"), max_length=255)
    slug = models.SlugField(_("slug"), unique=True)
    archived = models.BooleanField("Calendrier archivé", default=False)

    parent = models.ForeignKey(
        "Calendar",
        on_delete=models.SET_NULL,
        related_name="children",
        related_query_name="child",
        null=True,
        blank=True,
    )
    events = models.ManyToManyField("Event",
                                    related_name="calendars",
                                    through="CalendarItem")

    user_contributed = models.BooleanField(
        _("Les utilisateurs peuvent ajouter des événements"), default=False)

    description = models.TextField(
        _("description"),
        blank=True,
        help_text=_("Saisissez une description (HTML accepté)"),
    )

    image = StdImageField(
        _("bannière"),
        upload_to=FilePattern(
            filename_pattern=
            "{app_label}/{model_name}/{instance.name:slug}{ext}"),
        variations={
            "thumbnail": (400, 250),
            "banner": (1200, 400)
        },
        blank=True,
    )

    class Meta:
        verbose_name = _("Agenda")

    def __str__(self):
        return self.name

    def clean_fields(self, exclude=None):
        super().clean_fields()

        if exclude is None:
            exclude = []

        if "parent" not in exclude:
            calendar = self
            for i in range(settings.CALENDAR_MAXIMAL_DEPTH):
                calendar = calendar.parent

                if calendar is None:
                    break
            else:
                raise ValidationError({
                    "parent":
                    ValidationError(
                        _("Impossible d'utiliser ce calendrier comme parent :"
                          " cela excéderait la profondeur maximale autorisée.")
                    )
                })
Esempio n. 13
0
class EventSubtype(BaseSubtype):
    TYPE_GROUP_MEETING = "G"
    TYPE_PUBLIC_MEETING = "M"
    TYPE_PUBLIC_ACTION = "A"
    TYPE_OTHER_EVENTS = "O"

    TYPE_CHOICES = (
        (TYPE_GROUP_MEETING, _("Réunion de groupe")),
        (TYPE_PUBLIC_MEETING, _("Événement public")),
        (TYPE_PUBLIC_ACTION, _("Action publique")),
        (TYPE_OTHER_EVENTS, _("Autre")),
    )

    TYPES_PARAMETERS = {
        TYPE_GROUP_MEETING: {
            "color": "#4a64ac",
            "icon_name": "comments"
        },
        TYPE_PUBLIC_MEETING: {
            "color": "#e14b35",
            "icon_name": "bullhorn"
        },
        TYPE_PUBLIC_ACTION: {
            "color": "#c2306c",
            "icon_name": "exclamation"
        },
        TYPE_OTHER_EVENTS: {
            "color": "#49b37d",
            "icon_name": "calendar"
        },
    }

    TYPE_DESCRIPTION = {
        TYPE_GROUP_MEETING:
        _("Une réunion qui concerne principalement les membres du groupes, et non le public de"
          " façon générale. Par exemple, la réunion hebdomadaire du groupe, une réunion de travail,"
          " ou l'audition d'une association"),
        TYPE_PUBLIC_MEETING:
        _("Un événement ouvert à tous les publics, au-delà des membres du groupe, mais"
          " qui aura lieu dans un lieu privé. Par exemple, un événement public avec un orateur,"
          " une projection ou un concert."),
        TYPE_PUBLIC_ACTION:
        _("Une action qui se déroulera dans un lieu public et qui aura comme objectif principal"
          " d'aller à la rencontre ou d'atteindre des personnes extérieures à la FI"
          ),
        TYPE_OTHER_EVENTS:
        _("Tout autre type d'événement qui ne rentre pas dans les catégories ci-dessus."
          ),
    }

    type = models.CharField(_("Type d'événement"),
                            max_length=1,
                            choices=TYPE_CHOICES)

    default_description = DescriptionField(
        verbose_name=_("description par défaut"),
        blank=True,
        help_text=
        "La description par défaut pour les événements de ce sous-type.",
        allowed_tags=settings.ADMIN_ALLOWED_TAGS,
    )

    default_image = StdImageField(
        _("image par défaut"),
        upload_to=banner_path,
        variations=settings.BANNER_CONFIG,
        blank=True,
        help_text=_(
            "L'image associée par défaut à un événement de ce sous-type."),
    )

    class Meta:
        verbose_name = _("Sous-type d'événement")
        verbose_name_plural = _("Sous-types d'événement")

    def __str__(self):
        return self.description
Esempio n. 14
0
class ItUser(AbstractUser):

    # signed = models.ForeignKey('ItUser', null=True, blank=True)
    # colleagues = models.ForeignKey('ItUser', null=True, blank=True)
    # number = models.PositiveIntegerField(max_length=5)
    username = models.CharField(
        _('Логин'),
        max_length=150,
        unique=True,
        validators=[AbstractUser.username_validator],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_('Имя'), max_length=30)
    last_name = models.CharField(_('Фамилия'), max_length=30)
    specialty = models.CharField(_('Специальность'),
                                 choices=SPECIALITIES,
                                 max_length=100)
    birth = models.DateField(_('Дата рождения'), default=timezone.now)
    email = models.EmailField(_('Email'), unique=True)
    img = StdImageField(_("Аватар"),
                        upload_to=avatar_path,
                        variations={
                            'thumb': (150, 150, True),
                            'small': (50, 50, True)
                        },
                        validators=[MaxSizeValidator(1028, 768)],
                        null=True)
    city = models.CharField(_('Город'),
                            max_length=100,
                            choices=CITIES,
                            default='msk')
    experience = models.CharField(_('Опыт работы'), max_length=100)
    education = models.CharField(_('Образование'), max_length=100)
    about = models.TextField(_('О себе'), max_length=2000)
    skills = models.CharField(
        _('Навыки'),
        max_length=1000,
        help_text=
        _('Например: Python, Photoshop, CSS, Angular - разделять навыки запятыми'
          ))
    edu = models.BooleanField(_('Готов обучать'), default=False)
    edu_list = models.CharField(
        _('Могу обучить'),
        max_length=1000,
        help_text=_('Например: Python, Photoshop, CSS, Angular'),
        null=True,
        blank=True)
    github = models.CharField(max_length=100, blank=True)
    bitbacket = models.CharField("bitbucket", max_length=100, blank=True)
    pinterest = models.CharField(max_length=100, blank=True)
    facebook = models.CharField(max_length=100, blank=True)
    instagram = models.CharField(max_length=100, blank=True)
    twitter = models.CharField(max_length=100, blank=True)
    vk = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = 'ItUser'
        verbose_name_plural = 'ItUsers'

    def get_small_img_url(self):
        try:
            return self.img.small.url
        except AttributeError:
            return default_avatars.DEFAULT_USER_AVATAR_SMALL

    def get_thumb_img_url(self):
        try:
            return self.img.thumb.url
        except AttributeError:
            return default_avatars.DEFAULT_USER_AVATAR_THUMB

    def get_age(self):
        today = date.today()
        years_difference = today.year - self.birth.year
        is_before_birthday = (today.month, today.day) < (self.birth.month,
                                                         self.birth.day)
        elapsed_years = years_difference - int(is_before_birthday)
        return elapsed_years

    def get_skiils(self):
        str = self.skill.split(',')
        print(str)
        return list(str)
Esempio n. 15
0
class ProductBaseInfo(models.Model):

    CHOOSE = (
        ('on', '上架'),
        ('off', '下架'),
    )

    # 货号 ------- 主键
    productId = models.CharField(verbose_name='货号',
                                 max_length=100,
                                 primary_key=True)
    # 品名
    productName = models.CharField(verbose_name='商品名称', max_length=255)
    # 分类 ------- 关联类目信息
    productType = models.ForeignKey(PType,
                                    on_delete=models.CASCADE,
                                    verbose_name='商品分类',
                                    blank=False,
                                    default=1,
                                    related_name="product")
    # 系统编码
    systemCode = models.BigIntegerField(verbose_name='系统编码',
                                        blank=True,
                                        null=True)
    # 条形编码
    barCode = models.BigIntegerField(verbose_name='条形编码',
                                     blank=True,
                                     null=True)
    # 颜色
    color = models.CharField(verbose_name='颜色',
                             max_length=255,
                             blank=True,
                             null=True)
    # 规格
    norms = models.CharField(verbose_name='规格',
                             max_length=255,
                             blank=True,
                             null=True)
    # 重量
    weight = models.IntegerField(verbose_name='重量', blank=True, null=True)
    # 价格
    price = models.IntegerField(verbose_name='价格', blank=False)

    # 商品详情
    description = models.TextField(verbose_name='商品详情', blank=True, null=True)
    # 商品简介
    brief = models.TextField(verbose_name='商品简介',
                             blank=True,
                             default="这里是商品简介")
    # 品牌
    brand = models.TextField(verbose_name='品牌', blank=True, default="这里是商品牌")

    # 缩略图
    smallurl = StdImageField(verbose_name="图片路径",
                             upload_to='ProductPreViewPic',
                             variations={'nail': {
                                 'width': 100,
                                 'height': 75
                             }},
                             blank=True,
                             default='')

    quantity = models.IntegerField(verbose_name="虚拟库存",
                                   blank=True,
                                   default=500)

    shell = models.CharField(verbose_name='货架',
                             max_length=3,
                             choices=CHOOSE,
                             default='on',
                             blank=False)

    def __str__(self):
        return self.productId.__str__() + "[" + self.productName.__str__(
        ) + "]" + "(" + self.norms.__str__() + ")"

    class Meta:
        app_label = 'wechatapp'
        verbose_name = '商品详情'
        verbose_name_plural = '商品详情管理'

    def image_img(self):
        if self.smallurl:
            return str('<img src="%s" />' % self.smallurl.nail.url)
        else:
            return u'上传图片'

    image_img.short_description = '效果图'
    image_img.allow_tags = True
Esempio n. 16
0
class User(models.Model):
    """普通用户表"""

    gender = (
        ('male', '男'),
        ('female', '女'),
    )

    username = models.CharField(max_length=128,
                                verbose_name="用户名",
                                unique=True)
    password = models.CharField(max_length=256, verbose_name="密码")
    phone = models.CharField(max_length=13, verbose_name="手机号", blank=True)
    email = models.EmailField(blank=True, verbose_name="邮箱")
    sex = models.CharField(max_length=32,
                           choices=gender,
                           default='男',
                           verbose_name="性别",
                           blank=True)
    avatar = StdImageField(
        upload_to=UploadToUUID(path=datetime.now().strftime("avatar/%Y%m%d")),
        variations={
            'thumbnail': {
                'width': 100,
                'height': 75,
                'style': 'objects-fit:cover'
            }
        },
        null=True,
        blank=True,
        verbose_name="头像")
    background = StdImageField(
        upload_to=UploadToUUID(
            path=datetime.now().strftime("background/%Y%m%d")),
        variations={'thumbnail': {
            'width': 100,
            'height': 75
        }},
        null=True,
        blank=True,
        verbose_name="背景图")
    sign = models.CharField(max_length=64,
                            null=True,
                            blank=True,
                            verbose_name="一句话介绍",
                            default='该用户还没有个性签名')
    introduce = models.CharField(max_length=200,
                                 null=True,
                                 blank=True,
                                 verbose_name="个人简介",
                                 default='该用户还没有个人简介')
    c_time = models.DateTimeField(auto_now_add=True)
    tags = models.ManyToManyField(Tags, related_name='user_pick', blank=True)

    def avatar_img(self):
        if self.avatar:
            return str(
                '<img src="%s" width="60" height="60" style="object-fit: cover" />'
                % self.avatar.url)
        else:
            return ' '

    avatar_img.short_description = '头像'
    avatar_img.allow_tags = True

    def background_img(self):
        if self.background:
            return str(
                '<img src="%s" width="200" height="48"  style="object-fit:cover"/>'
                % self.background.url)
        else:
            return ' '

    background_img.short_description = '背景图'
    background_img.allow_tags = True

    def __str__(self):
        return self.username

    class Meta:
        ordering = ['c_time']
        verbose_name = '旅途用户'
        verbose_name_plural = '旅途用户'
Esempio n. 17
0
class Show(models.Model):
    class Meta:
        verbose_name = 'Show'
        verbose_name_plural = 'Shows'
        unique_together = ('name', 'start_date', 'end_date')

    name = models.CharField(max_length=255)

    slug = models.SlugField(
        blank=True,
        help_text=
        'Used in the URL of the detail page, leave blank to auto-generate.')

    location = models.CharField(
        max_length=30,
        default=config.DEFAULT_LOCATION,
        help_text=
        'Will show up alongside show, you can hide this with CSS if needed.')

    description = models.TextField(help_text='A short description', )

    long_description = models.TextField(
        blank=True,
        help_text=
        'Shows up on the detail page, this field is written in Markdown. ' +
        '(See <a href="http://www.darkcoding.net/software/markdown-quick-reference/">Markdown reference</a> for reference).'
    )

    poster = StdImageField(
        upload_to=UploadToClassNameDir(),
        blank=True,
        null=True,
        help_text=
        'Upload a large image, we will automatically create smaller versions to use.',
        variations={
            'poster_wall': (126, 178),
            'poster_page': (256, 362),
            'poster_tiny': (50, 71),
        })

    start_date = models.DateField()
    end_date = models.DateField()

    category = models.ForeignKey('Category')

    def date_formatted(self):
        return self.start_date.strftime('%A %d %B %Y')

    def is_current(self):
        return datetime.date.today() <= self.end_date

    def is_current_show(self):
        return (datetime.date.today() -
                datetime.timedelta(days=1)) <= self.end_date

    def show_sold_out(self):
        if self.has_occurrences():
            occ_max_sell_count = 0
            ticket_count = 0
            for occ in self.occurrence_set.all():
                ticket_count += occ.tickets_sold()
                occ_max_sell_count += occ.maximum_sell

            return ticket_count >= occ_max_sell_count
        else:
            return False

    def booking_closed(self):
        if len(Occurrence.objects.get_available(self)) > 0:
            return False
        else:
            return True

    # Get sale data for shows
    def get_sale_data(self):
        occs = Occurrence.objects.filter(show=self)
        totals = {
            'show_sales': 0,
            'total_sold': 0,
            'total_reserved': 0,
            'total_possible': 0
        }
        for oc in occs:
            totals['show_sales'] += oc.get_ticket_data()['total_profit']
            totals['total_sold'] += oc.get_ticket_data()['total_sold']
            totals['total_reserved'] += oc.tickets_sold()
            totals['total_possible'] += oc.maximum_sell
        return totals

    # Does a show have any occurrences
    def has_occurrences(self):
        return Occurrence.objects.filter(show=self).count() > 0

    def long_markdown(self):
        return Markdown().convert(self.long_description)

    def clean(self, *args, **kwargs):
        cleaned_data = super(Show, self).clean(*args, **kwargs)

        # Check to see if the dates are current
        if self.start_date and (self.start_date < datetime.date.today()):
            raise ValidationError(
                ('Please enter a start date which is not in the past'),
                code='invalid_show_start_date_past')
        if self.end_date and (self.end_date < datetime.date.today()):
            raise ValidationError(
                ('Please enter an end date which is not in the past'),
                code='invalid_show_end_date_past')

        # Check to see if dates require the use of timetravel
        if self.start_date and self.end_date:
            if self.end_date < self.start_date:
                raise ValidationError((
                    'Unless you have invented time travel, a show cannot end before it has started'
                ),
                                      code='invalid_show_dates')

        return cleaned_data

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        have_orig = False
        if self.pk:
            orig = Show.objects.get(pk=self.pk)
            have_orig = True
        super(Show, self).save(*args, **kwargs)

    def __str__(self):
        return self.name