class SchemalessFieldMixin(models.Model): custom_data = HStoreField(null=True, blank=True) class Meta: abstract = True
class User(AbstractBaseUser, PermissionsMixin): ACTIVATION_NONE = 0 ACTIVATION_USER = 1 ACTIVATION_ADMIN = 2 SUBSCRIPTION_NONE = 0 SUBSCRIPTION_NOTIFY = 1 SUBSCRIPTION_ALL = 2 SUBSCRIPTION_CHOICES = [ (SUBSCRIPTION_NONE, _("No")), (SUBSCRIPTION_NOTIFY, _("Notify")), (SUBSCRIPTION_ALL, _("Notify with e-mail")), ] LIMIT_INVITES_TO_NONE = 0 LIMIT_INVITES_TO_FOLLOWED = 1 LIMIT_INVITES_TO_NOBODY = 2 LIMIT_INVITES_TO_CHOICES = [ (LIMIT_INVITES_TO_NONE, _("Everybody")), (LIMIT_INVITES_TO_FOLLOWED, _("Users I follow")), (LIMIT_INVITES_TO_NOBODY, _("Nobody")), ] #This is my custom user logging feature: activity_array = ArrayField( ArrayField( models.DateTimeField(null=True, blank=True), size=2, null=True, ), null=True, ) # Note that "username" field is purely for shows. # When searching users by their names, always use lowercased string # and slug field instead that is normalized around DB engines # differences in case handling. username = models.CharField(max_length=30) slug = models.CharField(max_length=30, unique=True) # Misago stores user email in two fields: # "email" holds normalized email address # "email_hash" is lowercase hash of email address used to identify account # as well as enforcing on database level that no more than one user can be # using one email address email = models.EmailField(max_length=255, db_index=True) email_hash = models.CharField(max_length=32, unique=True) joined_on = models.DateTimeField(_("joined on"), default=timezone.now, db_index=True) joined_from_ip = models.GenericIPAddressField(null=True, blank=True) is_hiding_presence = models.BooleanField(default=False) rank = models.ForeignKey("Rank", null=True, blank=True, on_delete=models.deletion.PROTECT) title = models.CharField(max_length=255, null=True, blank=True) requires_activation = models.PositiveIntegerField(default=ACTIVATION_NONE) is_staff = models.BooleanField( _("staff status"), default=False, help_text=_("Designates whether the user can log into admin sites."), ) roles = models.ManyToManyField("misago_acl.Role") acl_key = models.CharField(max_length=12, null=True, blank=True) is_active = models.BooleanField( _("active"), db_index=True, default=True, help_text=_( "Designates whether this user should be treated as active. " "Unselect this instead of deleting accounts."), ) is_active_staff_message = models.TextField(null=True, blank=True) is_deleting_account = models.BooleanField(default=False) avatar_tmp = models.ImageField(max_length=255, upload_to=avatars_store.upload_to, null=True, blank=True) avatar_src = models.ImageField(max_length=255, upload_to=avatars_store.upload_to, null=True, blank=True) avatar_crop = models.CharField(max_length=255, null=True, blank=True) avatars = JSONField(null=True, blank=True) is_avatar_locked = models.BooleanField(default=False) avatar_lock_user_message = models.TextField(null=True, blank=True) avatar_lock_staff_message = models.TextField(null=True, blank=True) signature = models.TextField(null=True, blank=True) signature_parsed = models.TextField(null=True, blank=True) signature_checksum = models.CharField(max_length=64, null=True, blank=True) is_signature_locked = models.BooleanField(default=False) signature_lock_user_message = models.TextField(null=True, blank=True) signature_lock_staff_message = models.TextField(null=True, blank=True) followers = models.PositiveIntegerField(default=0) following = models.PositiveIntegerField(default=0) follows = models.ManyToManyField("self", related_name="followed_by", symmetrical=False) blocks = models.ManyToManyField("self", related_name="blocked_by", symmetrical=False) limits_private_thread_invites_to = models.PositiveIntegerField( default=LIMIT_INVITES_TO_NONE, choices=LIMIT_INVITES_TO_CHOICES) unread_private_threads = models.PositiveIntegerField(default=0) sync_unread_private_threads = models.BooleanField(default=False) subscribe_to_started_threads = models.PositiveIntegerField( default=SUBSCRIPTION_NONE, choices=SUBSCRIPTION_CHOICES) subscribe_to_replied_threads = models.PositiveIntegerField( default=SUBSCRIPTION_NONE, choices=SUBSCRIPTION_CHOICES) threads = models.PositiveIntegerField(default=0) posts = models.PositiveIntegerField(default=0, db_index=True) last_posted_on = models.DateTimeField(null=True, blank=True) profile_fields = HStoreField(default=dict) agreements = ArrayField(models.PositiveIntegerField(), default=list) sso_id = models.PositiveIntegerField(null=True, blank=True, unique=True) USERNAME_FIELD = "slug" REQUIRED_FIELDS = ["email"] objects = UserManager() class Meta: indexes = [ models.Index( name="misago_user_is_staff_part", fields=["is_staff"], condition=Q(is_staff=True), ), models.Index( name="misago_user_requires_acti_part", fields=["requires_activation"], condition=Q(requires_activation__gt=0), ), models.Index( name="misago_user_is_deleting_a_part", fields=["is_deleting_account"], condition=Q(is_deleting_account=True), ), ] def clean(self): self.username = self.normalize_username(self.username) self.email = UserManager.normalize_email(self.email) def lock(self): """locks user in DB, shortcut for locking user model in views""" return User.objects.select_for_update().get(pk=self.pk) def delete(self, *args, **kwargs): if kwargs.pop("delete_content", False): self.delete_content() username = kwargs.pop("anonymous_username", None) if username: self.anonymize_data(username) else: raise ValueError( "user.delete() requires 'anonymous_username' argument") delete_avatar(self) return super().delete(*args, **kwargs) def delete_content(self): from ..signals import delete_user_content delete_user_content.send(sender=self) def mark_for_delete(self): self.is_active = False self.is_deleting_account = True self.save(update_fields=["is_active", "is_deleting_account"]) def anonymize_data(self, anonymous_username): """Replaces username with anonymized one, then send anonymization signal. Items associated with this user then anonymize their user-specific data like username or IP addresses. """ self.username = anonymous_username self.slug = slugify(self.username) from ..signals import anonymize_user_data anonymize_user_data.send(sender=self) @property def requires_activation_by_admin(self): return self.requires_activation == self.ACTIVATION_ADMIN @property def requires_activation_by_user(self): return self.requires_activation == self.ACTIVATION_USER @property def can_be_messaged_by_everyone(self): preference = self.limits_private_thread_invites_to return preference == self.LIMIT_INVITES_TO_NONE @property def can_be_messaged_by_followed(self): preference = self.limits_private_thread_invites_to return preference == self.LIMIT_INVITES_TO_FOLLOWED @property def can_be_messaged_by_nobody(self): preference = self.limits_private_thread_invites_to return preference == self.LIMIT_INVITES_TO_NOBODY @property def has_valid_signature(self): return is_user_signature_valid(self) def get_absolute_url(self): return reverse("misago:user", kwargs={ "slug": self.slug, "pk": self.pk }) def get_username(self): """dirty hack: return real username instead of normalized slug""" return self.username def get_full_name(self): return self.username def get_short_name(self): return self.username def get_real_name(self): return self.profile_fields.get("real_name") def set_username(self, new_username, changed_by=None): new_username = self.normalize_username(new_username) if new_username != self.username: old_username = self.username self.username = new_username self.slug = slugify(new_username) if self.pk: changed_by = changed_by or self namechange = self.record_name_change(changed_by, new_username, old_username) from ..signals import username_changed username_changed.send(sender=self) return namechange def record_name_change(self, changed_by, new_username, old_username): return self.namechanges.create( new_username=new_username, old_username=old_username, changed_by=changed_by, changed_by_username=changed_by.username, ) def set_email(self, new_email): self.email = UserManager.normalize_email(new_email) self.email_hash = hash_email(new_email) def get_any_title(self): return self.title or self.rank.title or self.rank.name def get_roles(self): roles_pks = [] roles_dict = {} for role in self.roles.all(): roles_pks.append(role.pk) role.origin = self roles_dict[role.pk] = role if self.rank: for role in self.rank.roles.all(): if role.pk not in roles_pks: role.origin = self.rank roles_pks.append(role.pk) roles_dict[role.pk] = role return [roles_dict[r] for r in sorted(roles_pks)] def update_acl_key(self): roles_pks = [] for role in self.get_roles(): if role.origin == "self": roles_pks.append("u%s" % role.pk) else: roles_pks.append("%s:%s" % (self.rank.pk, role.pk)) self.acl_key = md5(",".join(roles_pks).encode()).hexdigest()[:12] def email_user(self, subject, message, from_email=None, **kwargs): """sends an email to this user (for compat with Django)""" send_mail(subject, message, from_email, [self.email], **kwargs) def is_following(self, user_or_id): try: user_id = user_or_id.id except AttributeError: user_id = user_or_id try: self.follows.get(id=user_id) return True except User.DoesNotExist: return False def is_blocking(self, user_or_id): try: user_id = user_or_id.id except AttributeError: user_id = user_or_id try: self.blocks.get(id=user_id) return True except User.DoesNotExist: return False
class Product(models.Model, ItemRange, index.Indexed): product_class = models.ForeignKey(ProductClass, related_name='products', verbose_name=pgettext_lazy( 'Product field', 'product class')) product_tax = models.ForeignKey(ProductTax, related_name='producttax', blank=True, null=True, verbose_name=pgettext_lazy( 'Product field', 'product class')) name = models.CharField(pgettext_lazy('Product field', 'name'), unique=True, max_length=128) description = models.TextField(verbose_name=pgettext_lazy( 'Product field', 'description'), blank=True, null=True) categories = models.ManyToManyField(Category, verbose_name=pgettext_lazy( 'Product field', 'categories'), related_name='products') price = PriceField(pgettext_lazy('Product field', 'price'), currency=settings.DEFAULT_CURRENCY, max_digits=12, validators=[MinValueValidator(0)], default=Decimal(0), decimal_places=2) wholesale_price = PriceField(pgettext_lazy('Product field', 'Wholesale price'), currency=settings.DEFAULT_CURRENCY, blank=True, null=True, max_digits=12, decimal_places=2) product_supplier = models.ForeignKey(Supplier, related_name='suppliers', blank=True, null=True, verbose_name=pgettext_lazy( 'Product field', 'product supplier')) available_on = models.DateField(pgettext_lazy('Product field', 'available on'), blank=True, null=True) attributes = HStoreField(pgettext_lazy('Product field', 'attributes'), default={}) updated_at = models.DateTimeField(pgettext_lazy('Product field', 'updated at'), auto_now=True, null=True) is_featured = models.BooleanField(pgettext_lazy('Product field', 'is featured'), default=False) low_stock_threshold = models.IntegerField( pgettext_lazy('Product field', 'low stock threshold'), validators=[MinValueValidator(0)], null=True, blank=True, default=Decimal(10)) objects = ProductManager() search_fields = [ index.SearchField('name', partial_match=True), index.SearchField('description'), index.FilterField('available_on') ] class Meta: app_label = 'product' verbose_name = pgettext_lazy('Product model', 'product') verbose_name_plural = pgettext_lazy('Product model', 'products') def __iter__(self): if not hasattr(self, '__variants'): setattr(self, '__variants', self.variants.all()) return iter(getattr(self, '__variants')) def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % (class_.__module__, class_.__name__, self.pk, self.name) def __str__(self): return self.name def get_absolute_url(self): return reverse('product:details', kwargs={ 'slug': self.get_slug(), 'product_id': self.id }) def get_slug(self): return slugify(smart_text(unidecode(self.name))) def get_product_tax(self): return self.product_tax def get_tax_value(self): return self.product_tax.get_tax() def is_in_stock(self): return any(variant.is_in_stock() for variant in self) def total_stock(self): return Sum(self.variant.stock.stock_available()) def total_variants(self): return len(self.variants.all()) def get_first_category(self): for category in self.categories.all(): if not category.hidden: return category return None def get_variants_count(self): variants = self.variants.filter(product=self.pk) total = 0 for stock in variants: total += stock.get_stock_quantity() return total def is_available(self): today = datetime.date.today() return self.available_on is None or self.available_on <= today def get_first_image(self): first_image = self.images.first() if first_image: return first_image.image return None def get_attribute(self, pk): return self.attributes.get(smart_text(pk)) def set_attribute(self, pk, value_pk): self.attributes[smart_text(pk)] = smart_text(value_pk) def get_price_range(self, discounts=None, **kwargs): if not self.variants.exists(): price = calculate_discounted_price(self, self.price, discounts, **kwargs) return PriceRange(price, price) else: return super(Product, self).get_price_range(discounts=discounts, **kwargs)
class Product(SeoModel): product_type = models.ForeignKey(ProductType, related_name='products', on_delete=models.CASCADE) name = models.CharField(max_length=128) description = models.TextField() category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE) price = MoneyField(currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=settings.DEFAULT_DECIMAL_PLACES) available_on = models.DateField(blank=True, null=True) is_published = models.BooleanField(default=True) attributes = HStoreField(default={}, blank=True) updated_at = models.DateTimeField(auto_now=True, null=True) is_featured = models.BooleanField(default=False) charge_taxes = models.BooleanField(default=True) tax_rate = models.CharField(max_length=128, default=DEFAULT_TAX_RATE_NAME, blank=True) objects = ProductQuerySet.as_manager() class Meta: app_label = 'product' permissions = (('view_product', pgettext_lazy('Permission description', 'Can view products')), ('edit_product', pgettext_lazy('Permission description', 'Can edit products')), ('view_properties', pgettext_lazy('Permission description', 'Can view product properties')), ('edit_properties', pgettext_lazy('Permission description', 'Can edit product properties'))) def __iter__(self): if not hasattr(self, '__variants'): setattr(self, '__variants', self.variants.all()) return iter(getattr(self, '__variants')) def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % (class_.__module__, class_.__name__, self.pk, self.name) def __str__(self): return self.name def get_absolute_url(self): return reverse('product:details', kwargs={ 'slug': self.get_slug(), 'product_id': self.id }) def get_slug(self): return slugify(smart_text(unidecode(self.name))) def is_in_stock(self): return any(variant.is_in_stock() for variant in self) def is_available(self): today = datetime.date.today() return self.available_on is None or self.available_on <= today def get_first_image(self): first_image = self.images.first() return first_image.image if first_image else None def get_price_range(self, discounts=None, taxes=None): if self.variants.exists(): prices = [ variant.get_price(discounts=discounts, taxes=taxes) for variant in self ] return TaxedMoneyRange(min(prices), max(prices)) price = calculate_discounted_price(self, self.price, discounts) if not self.charge_taxes: taxes = None tax_rate = self.tax_rate or self.product_type.tax_rate price = apply_tax_to_price(taxes, tax_rate, price) return TaxedMoneyRange(start=price, stop=price)
class User(AbstractBaseUser, PermissionsMixin): ACTIVATION_NONE = 0 ACTIVATION_USER = 1 ACTIVATION_ADMIN = 2 SUBSCRIBE_NONE = 0 SUBSCRIBE_NOTIFY = 1 SUBSCRIBE_ALL = 2 SUBSCRIBE_CHOICES = [ (SUBSCRIBE_NONE, _("No")), (SUBSCRIBE_NOTIFY, _("Notify")), (SUBSCRIBE_ALL, _("Notify with e-mail")), ] LIMIT_INVITES_TO_NONE = 0 LIMIT_INVITES_TO_FOLLOWED = 1 LIMIT_INVITES_TO_NOBODY = 2 LIMIT_INVITES_TO_CHOICES = [ (LIMIT_INVITES_TO_NONE, _("Everybody")), (LIMIT_INVITES_TO_FOLLOWED, _("Users I follow")), (LIMIT_INVITES_TO_NOBODY, _("Nobody")), ] # Note that "username" field is purely for shows. # When searching users by their names, always use lowercased string # and slug field instead that is normalized around DB engines # differences in case handling. username = models.CharField(max_length=30) slug = models.CharField(max_length=30, unique=True) # Misago stores user email in two fields: # "email" holds normalized email address # "email_hash" is lowercase hash of email address used to identify account # as well as enforcing on database level that no more than one user can be # using one email address email = models.EmailField(max_length=255, db_index=True) email_hash = models.CharField(max_length=32, unique=True) joined_on = models.DateTimeField(_('joined on'), default=timezone.now) joined_from_ip = models.GenericIPAddressField() last_ip = models.GenericIPAddressField(null=True, blank=True) is_hiding_presence = models.BooleanField(default=False) rank = models.ForeignKey( 'Rank', null=True, blank=True, on_delete=models.deletion.PROTECT, ) title = models.CharField(max_length=255, null=True, blank=True) requires_activation = models.PositiveIntegerField(default=ACTIVATION_NONE) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_('Designates whether the user can log into admin sites.'), ) roles = models.ManyToManyField('misago_acl.Role') acl_key = models.CharField(max_length=12, null=True, blank=True) is_active = models.BooleanField( _('active'), db_index=True, default=True, help_text=_( "Designates whether this user should be treated as active. " "Unselect this instead of deleting accounts."), ) is_active_staff_message = models.TextField(null=True, blank=True) avatar_tmp = models.ImageField( max_length=255, upload_to=avatars.store.upload_to, null=True, blank=True, ) avatar_src = models.ImageField( max_length=255, upload_to=avatars.store.upload_to, null=True, blank=True, ) avatar_crop = models.CharField(max_length=255, null=True, blank=True) avatars = JSONField(null=True, blank=True) is_avatar_locked = models.BooleanField(default=False) avatar_lock_user_message = models.TextField(null=True, blank=True) avatar_lock_staff_message = models.TextField(null=True, blank=True) signature = models.TextField(null=True, blank=True) signature_parsed = models.TextField(null=True, blank=True) signature_checksum = models.CharField(max_length=64, null=True, blank=True) is_signature_locked = models.BooleanField(default=False) signature_lock_user_message = models.TextField(null=True, blank=True) signature_lock_staff_message = models.TextField(null=True, blank=True) followers = models.PositiveIntegerField(default=0) following = models.PositiveIntegerField(default=0) follows = models.ManyToManyField( 'self', related_name='followed_by', symmetrical=False, ) blocks = models.ManyToManyField( 'self', related_name='blocked_by', symmetrical=False, ) limits_private_thread_invites_to = models.PositiveIntegerField( default=LIMIT_INVITES_TO_NONE, choices=LIMIT_INVITES_TO_CHOICES, ) unread_private_threads = models.PositiveIntegerField(default=0) sync_unread_private_threads = models.BooleanField(default=False) subscribe_to_started_threads = models.PositiveIntegerField( default=SUBSCRIBE_NONE, choices=SUBSCRIBE_CHOICES, ) subscribe_to_replied_threads = models.PositiveIntegerField( default=SUBSCRIBE_NONE, choices=SUBSCRIBE_CHOICES, ) threads = models.PositiveIntegerField(default=0) posts = models.PositiveIntegerField(default=0, db_index=True) last_posted_on = models.DateTimeField(null=True, blank=True) profile_fields = HStoreField(default=dict) USERNAME_FIELD = 'slug' REQUIRED_FIELDS = ['email'] objects = UserManager() class Meta: indexes = [ PgPartialIndex( fields=['is_staff'], where={'is_staff': True}, ), PgPartialIndex( fields=['requires_activation'], where={'requires_activation__gt': 0}, ), ] def clean(self): self.username = self.normalize_username(self.username) self.email = UserManager.normalize_email(self.email) def lock(self): """locks user in DB, shortcut for locking user model in views""" return User.objects.select_for_update().get(pk=self.pk) def delete(self, *args, **kwargs): if kwargs.pop('delete_content', False): self.delete_content() avatars.delete_avatar(self) return super(User, self).delete(*args, **kwargs) def delete_content(self): from misago.users.signals import delete_user_content delete_user_content.send(sender=self) @property def acl_cache(self): try: return self._acl_cache except AttributeError: self._acl_cache = get_user_acl(self) return self._acl_cache @acl_cache.setter def acl_cache(self, value): raise TypeError("acl_cache can't be assigned") @property def acl_(self): raise NotImplementedError('user.acl_ property was renamed to user.acl') @property def requires_activation_by_admin(self): return self.requires_activation == self.ACTIVATION_ADMIN @property def requires_activation_by_user(self): return self.requires_activation == self.ACTIVATION_USER @property def can_be_messaged_by_everyone(self): preference = self.limits_private_thread_invites_to return preference == self.LIMIT_INVITES_TO_NONE @property def can_be_messaged_by_followed(self): preference = self.limits_private_thread_invites_to return preference == self.LIMIT_INVITES_TO_FOLLOWED @property def can_be_messaged_by_nobody(self): preference = self.limits_private_thread_invites_to return preference == self.LIMIT_INVITES_TO_NOBODY @property def has_valid_signature(self): return is_user_signature_valid(self) def get_absolute_url(self): return reverse('misago:user', kwargs={ 'slug': self.slug, 'pk': self.pk, }) def get_username(self): """dirty hack: return real username instead of normalized slug""" return self.username def get_full_name(self): return self.username def get_short_name(self): return self.username def set_username(self, new_username, changed_by=None): new_username = self.normalize_username(new_username) if new_username != self.username: old_username = self.username self.username = new_username self.slug = slugify(new_username) if self.pk: changed_by = changed_by or self self.record_name_change(changed_by, new_username, old_username) from misago.users.signals import username_changed username_changed.send(sender=self) def record_name_change(self, changed_by, new_username, old_username): self.namechanges.create( new_username=new_username, old_username=old_username, changed_by=changed_by, changed_by_username=changed_by.username, ) def set_email(self, new_email): self.email = UserManager.normalize_email(new_email) self.email_hash = hash_email(new_email) def get_any_title(self): return self.title or self.rank.title or self.rank.name def get_roles(self): roles_pks = [] roles_dict = {} for role in self.roles.all(): roles_pks.append(role.pk) role.origin = self roles_dict[role.pk] = role if self.rank: for role in self.rank.roles.all(): if role.pk not in roles_pks: role.origin = self.rank roles_pks.append(role.pk) roles_dict[role.pk] = role return [roles_dict[r] for r in sorted(roles_pks)] def update_acl_key(self): roles_pks = [] for role in self.get_roles(): if role.origin == 'self': roles_pks.append('u%s' % role.pk) else: roles_pks.append('%s:%s' % (self.rank.pk, role.pk)) self.acl_key = md5(','.join(roles_pks).encode()).hexdigest()[:12] def email_user(self, subject, message, from_email=None, **kwargs): """sends an email to this user (for compat with Django)""" send_mail(subject, message, from_email, [self.email], **kwargs) def is_following(self, user): try: self.follows.get(pk=user.pk) return True except User.DoesNotExist: return False def is_blocking(self, user): try: self.blocks.get(pk=user.pk) return True except User.DoesNotExist: return False
class Model(models.Model): json = JSONField() hstore = HStoreField() class Meta: app_label = "django_tables2_test"
class SendGridMail(Model): """用SendGrid寄出的信件紀錄 * case: 案件 * template: 信件樣板 * from_email: 寄送者 * to_email: 收件者 * data: SendGrid Transactional Templates所使用的json資料,使用Postgres的HStoreField儲存, 詳見https://docs.djangoproject.com/en/2.1/ref/contrib/postgres/fields/#hstorefield * success: API請求狀態,若status_code回傳202則紀錄為成功 * send_time: 發送時間 """ case = ForeignKey('cases.Case', on_delete=CASCADE, related_name='sendgrid_mails', verbose_name=_('Case')) template = ForeignKey('mails.SendGridMailTemplate', on_delete=CASCADE, related_name='mails', verbose_name=_('SendGrid Template')) from_email = EmailField(verbose_name=_('From Email')) to_email = EmailField(verbose_name=_('To Email')) data = HStoreField(verbose_name=_('Mail Data')) success = BooleanField(default=False, verbose_name=_('Request Success')) send_time = DateTimeField(auto_now=True, null=True, blank=True, verbose_name=_('Send Time')) class Meta: verbose_name = _('SendGrid Mail') verbose_name_plural = _('SendGrid Mails') ordering = ('-send_time', ) def __str__(self): return self.to_email def save(self, *args, **kwargs): if not self.pk: self.from_email = self.from_email or settings.SERVER_EMAIL self.to_email = self.to_email or self.case.email if self.to_email: try: response = SendGridMail.send_template(self.from_email, self.to_email, self.data, self.template.tid) self.success = bool(response and response.status_code == 202) except SendGridMailTemplate.DoesNotExist as e: sendgrid_system_mail(e) super(SendGridMail, self).save(*args, **kwargs) def send(self): self.save() @staticmethod def send_template(from_email, to_email, data, template_id): """Call Sendgrid Transactional Template API""" if from_email == settings.SERVER_EMAIL: from_email = Email(from_email, name=settings.SERVER_EMAIL_NAME) else: from_email = Email(from_email) mail = Mail(from_email=from_email, to_email=Email(to_email)) mail.personalizations[0].dynamic_template_data = json.loads( json.dumps(data, cls=DjangoJSONEncoder)) mail.template_id = template_id try: return sg.client.mail.send.post(request_body=mail.get()) except HTTPError as e: sendgrid_system_mail(e) return None
class Product(SeoModel, PublishableModel): product_type = models.ForeignKey(ProductType, related_name="products", on_delete=models.CASCADE) name = models.CharField(max_length=128) description = models.TextField(blank=True) description_json = JSONField(blank=True, default=dict) category = models.ForeignKey(Category, related_name="products", on_delete=models.CASCADE) price = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, ) attributes = HStoreField(default=dict, blank=True) updated_at = models.DateTimeField(auto_now=True, null=True) charge_taxes = models.BooleanField(default=True) tax_rate = models.CharField(max_length=128, blank=True, choices=TaxRateType.CHOICES) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) objects = ProductsQueryset.as_manager() translated = TranslationProxy() class Meta: app_label = "product" ordering = ("name", ) permissions = (( "manage_products", pgettext_lazy("Permission description", "Manage products."), ), ) def __iter__(self): if not hasattr(self, "__variants"): setattr(self, "__variants", self.variants.all()) return iter(getattr(self, "__variants")) def __repr__(self): class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, ) def __str__(self): return self.name @property def is_available(self): return self.is_visible and self.is_in_stock() def get_absolute_url(self): return reverse("product:details", kwargs={ "slug": self.get_slug(), "product_id": self.id }) def get_slug(self): return slugify(smart_text(unidecode(self.name))) def is_in_stock(self): return any(variant.is_in_stock() for variant in self) def get_first_image(self): images = list(self.images.all()) return images[0] if images else None def get_price_range(self, discounts: Iterable[DiscountInfo] = None, taxes=None): if self.variants.all(): prices = [ variant.get_price(discounts=discounts, taxes=taxes) for variant in self ] return TaxedMoneyRange(min(prices), max(prices)) price = calculate_discounted_price(self, self.price, discounts) if not self.charge_taxes: taxes = None tax_rate = self.tax_rate or self.product_type.tax_rate price = apply_tax_to_price(taxes, tax_rate, price) return TaxedMoneyRange(start=price, stop=price)
class AhomeJobTemplate(CreatedUpdatedModel): """ Template for all ahome Job related modules """ class Meta: abstract = True name = models.CharField(max_length=100, blank=True) label = models.CharField(max_length=200, blank=True) description = models.CharField(max_length=200, blank=True, null=True) action = models.CharField(max_length=200, blank=True, choices=ACTION_CHOICES, default='start') ahomefile = JSONField(blank=True, default=dict, editable=True, null=True) project = models.ForeignKey(Project, related_name='%(class)ss', on_delete=models.DO_NOTHING, blank=True, null=True) #organization = models.ForeignKey(ORGANISATION_MODEL, related_name='%(class)ss', on_delete=models.PROTECT, blank=True, null=True) #infrastructure= models.ForeignKey(INFRASTRUCTURE_MODEL, related_name='%(class)ss', on_delete=models.PROTECT, blank=True, null=True) iaas = models.ForeignKey(INFRASTRUCTURE_MODEL, related_name='%(class)ss', on_delete=models.PROTECT, blank=True, null=True) target = models.CharField(max_length=200, blank=True) kind = models.CharField(max_length=200, default='generic') hosted = models.CharField(max_length=200, blank=True) opts = JSONField(blank=True, default=dict, editable=True, null=True) status = models.CharField(max_length=200, blank=True, choices=STATUS_CHOICES, default='running') output = models.CharField(max_length=200, blank=True) ident = models.CharField(max_length=200, blank=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False) cloud = models.BooleanField(verbose_name=_("cloud based"), default=False) unique_keys = HStoreField(blank=True, default=dict, null=True, verbose_name=_("unique keys"), help_text=_("Unique key(s) for ansible callback")) source = JSONField(blank=True, default=dict, editable=True, null=True) # todo Encript this field credentials = JSONField(blank=True, default=dict, editable=True, null=True) definition = JSONField(blank=True, default=dict, editable=True, null=True) facts = JSONField(blank=True, default=dict, editable=True, null=True) setfacts = JSONField(blank=True, default=dict, editable=True, null=True) inputs = JSONField(blank=True, default=dict, editable=True, null=True) applications = ArrayField(models.CharField(max_length=200), blank=True, default=list) schema = JSONField(blank=True, default=dict, editable=True, null=True) runner = JSONField(blank=True, default=dict, editable=True, null=True) tags = models.ManyToManyField('Tag', verbose_name=_("tags"), blank=True, help_text=_("Select tag(s)")) # tags = ArrayField(models.CharField(max_length=200), blank=True) def save(self, *args, **kwargs): if self.kind: if self.kind in CLOUD_LISTS: self.cloud = True if not self.schema: self.schema = DEFAULT_CREDENTIAL_SCHEMA if not self.unique_keys: self.unique_keys = dict(uuid=self.uuid) return super().save(*args, **kwargs) def live_time(self, since=None): from sysutils.utils.timeutils import sting_to_date, date_time_now, display_time now = date_time_now() seconds = (now - self.created).total_seconds() hours = round(seconds / (60 * 60), 2) return { 'seconds': seconds, 'hours': hours, 'human': display_time(seconds) } # def pre_create(self, user, *args, **kwargs): # try: # # self.owner = user # form_data = kwargs.get('form_data') # _logger.debug("PRE_CREATE - FORM DATA \n{}".format(pprint.pformat(form_data, indent=4))) # except: # pass # pass
class Unit(models.Model): id = models.IntegerField(primary_key=True) public = models.BooleanField(null=False, default=True) location = models.PointField(null=True, srid=PROJECTION_SRID) # lat, lng? geometry = models.GeometryField(srid=PROJECTION_SRID, null=True) department = models.ForeignKey(Department, null=True) root_department = models.ForeignKey(Department, null=True, related_name='descendant_units') organizer_type = models.PositiveSmallIntegerField(choices=ORGANIZER_TYPES, null=True) organizer_name = models.CharField(max_length=150, null=True) organizer_business_id = models.CharField(max_length=10, null=True) provider_type = models.PositiveSmallIntegerField(choices=PROVIDER_TYPES, null=True) contract_type = models.PositiveSmallIntegerField(choices=CONTRACT_TYPES, null=True) picture_url = models.URLField(max_length=250, null=True) picture_entrance_url = models.URLField(max_length=500, null=True) streetview_entrance_url = models.URLField(max_length=500, null=True) description = models.TextField(null=True) short_description = models.TextField(null=True) name = models.CharField(max_length=200, db_index=True) street_address = models.CharField(max_length=100, null=True) www = models.URLField(max_length=400, null=True) address_postal_full = models.CharField(max_length=100, null=True) call_charge_info = models.CharField(max_length=100, null=True) picture_caption = models.TextField(null=True) phone = models.CharField(max_length=120, null=True) fax = models.CharField(max_length=50, null=True) email = models.EmailField(max_length=100, null=True) accessibility_phone = models.CharField(max_length=50, null=True) accessibility_email = models.EmailField(max_length=100, null=True) accessibility_www = models.URLField(max_length=400, null=True) created_time = models.DateTimeField(null=True) # ASK API: are these UTC? no Z in output municipality = models.ForeignKey(Municipality, null=True, db_index=True) address_zip = models.CharField(max_length=10, null=True) data_source = models.CharField(max_length=30, null=True) extensions = HStoreField(null=True) last_modified_time = models.DateTimeField(db_index=True, help_text='Time of last modification') service_nodes = models.ManyToManyField("ServiceNode", related_name='units') services = models.ManyToManyField("Service", related_name='units', through='UnitServiceDetails') keywords = models.ManyToManyField(Keyword) connection_hash = models.CharField(max_length=40, null=True, help_text='Automatically generated hash of connection info') accessibility_property_hash = models.CharField( max_length=40, null=True, help_text='Automatically generated hash of accessibility property info') identifier_hash = models.CharField(max_length=40, null=True, help_text='Automatically generated hash of other identifiers') service_details_hash = models.CharField(max_length=40, null=True) accessibility_viewpoints = JSONField(default="{}", null=True) # Cached fields for better performance root_service_nodes = models.CharField(max_length=50, null=True, validators=[validate_comma_separated_integer_list]) objects = models.GeoManager() search_objects = UnitSearchManager() class Meta: ordering = ['-pk'] def __str__(self): return "%s (%s)" % (get_translated(self, 'name'), self.id) def get_root_service_nodes(self): from .service_node import ServiceNode tree_ids = self.service_nodes.all().values_list('tree_id', flat=True).distinct() qs = ServiceNode.objects.filter(level=0).filter(tree_id__in=list(tree_ids)) service_node_list = qs.values_list('id', flat=True).distinct() return sorted(service_node_list)
class Shapefile(models.Model): url = models.URLField(unique=True) etags = HStoreField(default=dict)
class Mapping(models.Model): # MappingID = models.IntegerField(primary_key = True) UserID = models.ForeignKey('Usr.Usr', on_delete = models.RESTRICT) MappingFor = models.TextField(null = True) CreatedOn = models.DateField(auto_now_add = True, null = True) Mappings = HStoreField(null=True)
class MLData(models.Model): fe = models.ForeignKey(FeatExtractedData, on_delete=models.CASCADE) parameters = HStoreField(null=True, default=None) path_to_file = models.CharField(max_length=250)#FileField()
def test_not_a_string(self): field = HStoreField() with self.assertRaises(exceptions.ValidationError) as cm: field.clean({'a': 1}, None) self.assertEqual(cm.exception.code, 'not_a_string') self.assertEqual(cm.exception.message % cm.exception.params, 'The value of "a" is not a string.')
class Section(models.Model): exam = models.ForeignKey(Exam, related_name='sections', null=False) SECTION_CHOICES = (('MC', 'Multiple Choice'), ('E', 'Essay'), ('M', 'Matching'), ('TF', 'True False'), ('FB', 'Fill in the Blank')) section_type = models.CharField(max_length=2, choices=SECTION_CHOICES, default='E') SECTION_TEMPLATES = ( ('E', 'exams/essay_question_template.html'), ('MC', 'exams/mc_question_template.html'), ('M', 'exams/matching_question_template.html'), ('TF', 'exams/tf_question_template.html'), ('FB', 'exams/fitb_question_template.html'), ) SECTION_FORM_TEMPLATES = ( ('E', 'exams/essay_question.html'), ('M', 'exams/matching_question.html'), ('TF', 'exams/tf_question.html'), ('FB', 'exams/fitb_question.html'), ('MC', 'exams/mc_question.html'), ) @property def question_template(self): return dict(Section.SECTION_TEMPLATES)[self.section_type] # Instructions instructions = models.TextField(null=True, blank=True) # Required number of questions needed to submit exam required_number_to_submit = models.IntegerField(default=0) # First section in exam has a section_index of 0 section_index = models.IntegerField(default=0) first_question_index = models.IntegerField(default=1) question_count = models.IntegerField() questions = HStoreField(null=True) def __unicode__(self): try: return "Section %s [%s] for %s" % ( self.section_index, self.get_section_type_display(), self.exam) except AttributeError as e: return str(self.id) + ": " + str(e) class Meta: ordering = ['exam', 'section_index'] unique_together = ('exam', 'section_index') def autograde(self, response): if self.section_type != 'E': responses = response.responses score_for_section = 0 for i in range(1, self.question_count + 1): question_data = ast.literal_eval(self.questions[str(i)]) # see if response of trainee equals answer; if it does assign point if self.section_type == 'FB': responses_to_blanks = responses[str(i)].replace( '\"', '').lower().split('$') answers_to_blanks = str( question_data["answer"]).lower().split('$') total_blanks = len(responses_to_blanks) number_correct = 0 for i in range(0, total_blanks): try: if responses_to_blanks[i] == answers_to_blanks[i]: number_correct += 1 except IndexError: continue # TODO: convert to decimal blank_weight = float( question_data["points"]) / float(total_blanks) score_for_section += (number_correct * blank_weight) # Everything else other than FB and Essay can be automatically graded with this elif (responses[str(i)].replace('\"', '').lower() == str( question_data["answer"]).lower()): score_for_section += int(question_data["points"]) response.score = score_for_section else: response.comments = "NOT GRADED YET" # Finally save the response response.save() return response.score if response.score else 0
class FeatExtractedData(models.Model): pp_recording = models.ForeignKey(Preprocessed_Recording, on_delete=models.CASCADE) parameters = HStoreField(null=True, default=None) path_to_file = models.CharField(max_length=250)#FileField() # To become FileField...
class Thumbnail(models.Model, File): user = models.ForeignKey(User, related_name='thumbnails_at_user', null=True) thumbnail_file = models.CharField(max_length=2000) thumbnail_sizes = HStoreField(null=True) @property def key(self): return self.thumbnail_file @key.setter def key(self, value): self.thumbnail_file = value @staticmethod @app.task def thumbnail_from_url(url, key, thumbnail, sizes=settings.THUMBNAIL_SIZES): # download the file filename, _ = urllib.urlretrieve(url) # TODO use os.path.splitext: # https://docs.python.org/2/library/os.path.html#os.path.splitext ext = filename.rpartition('.')[2] image = Image.open(filename) # jpegs are rotated according to their exif data if ext in ['jpeg', 'jpg']: image = rotate_jpg_from_exif(image) ext = JPEG_FILE_EXTENSION # create a thumbnail for size for label, size in sizes.iteritems(): if ext == GIF_FILE_EXTENSION: # NOTE: resize_gif_with_ratio is calling save on img internally thumb_image = resize_gif_with_ratio(image, size, StringIO()) else: thumb_image = StringIO() # NOTE: while we're calling it manually here _thumb_image = resize_single_image_with_ratio(image, size) # NOTE: For thumbnails, we want to convert all supported image # formats (with the exception of gif) to jpeg, as it # generally does the best job in compression for all types # of images. ext = JPEG_FILE_EXTENSION _thumb_image.format = ext _thumb_image.save(thumb_image, ext) # upload to aws aws_key = Key(aws.get_bucket()) aws_key.key = Thumbnail.build_aws_key(key, label, ext) aws_key.set_contents_from_string(thumb_image.getvalue()) aws_key.make_public() # update thumbnail if label == settings.THUMBNAIL_SIZE_DEFAULT and thumbnail.thumbnail_file == settings.THUMBNAIL_DEFAULT: thumbnail.thumbnail_file = aws_key.key thumbnail.thumbnail_sizes[label] = File.url_safe_from_key( aws_key.key) thumbnail.save() logger.info('Thumbnail created and uploaded to: \n{}'.format( thumbnail.thumbnail_sizes[label])) return thumbnail @staticmethod def build_aws_key(key, label, extension): return '{}_thumbnails/{}/{}.{}'.format(key, label, str(uuid.uuid4()), extension)
class ProductVariant(models.Model): sku = models.CharField(max_length=32, unique=True) name = models.CharField(max_length=255, blank=True) price_override = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True, ) product = models.ForeignKey(Product, related_name="variants", on_delete=models.CASCADE) attributes = HStoreField(default=dict, blank=True) images = models.ManyToManyField("ProductImage", through="VariantImage") track_inventory = models.BooleanField(default=True) quantity = models.IntegerField(validators=[MinValueValidator(0)], default=Decimal(1)) quantity_allocated = models.IntegerField(validators=[MinValueValidator(0)], default=Decimal(0)) cost_price = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True, ) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) translated = TranslationProxy() class Meta: app_label = "product" def __str__(self): return self.name or self.sku @property def quantity_available(self): return max(self.quantity - self.quantity_allocated, 0) @property def is_visible(self): return self.product.is_visible @property def is_available(self): return self.product.is_available def check_quantity(self, quantity): """Check if there is at least the given quantity in stock if stock handling is enabled. """ if self.track_inventory and quantity > self.quantity_available: raise InsufficientStock(self) @property def base_price(self): return (self.price_override if self.price_override is not None else self.product.price) def get_price(self, discounts: Iterable[DiscountInfo] = None, taxes=None): price = calculate_discounted_price(self.product, self.base_price, discounts) if not self.product.charge_taxes: taxes = None tax_rate = self.product.tax_rate or self.product.product_type.tax_rate return apply_tax_to_price(taxes, tax_rate, price) def get_weight(self): return self.weight or self.product.weight or self.product.product_type.weight def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse("product:details", kwargs={ "slug": slug, "product_id": product_id }) def is_shipping_required(self): return self.product.product_type.is_shipping_required def is_digital(self): is_digital = self.product.product_type.is_digital return not self.is_shipping_required() and is_digital def is_in_stock(self): return self.quantity_available > 0 def display_product(self, translated=False): if translated: product = self.product.translated variant_display = str(self.translated) else: variant_display = str(self) product = self.product product_display = ("%s (%s)" % (product, variant_display) if variant_display else str(product)) return smart_text(product_display) def get_first_image(self): images = list(self.images.all()) return images[0] if images else self.product.get_first_image() def get_ajax_label(self, discounts=None): price = self.get_price(discounts).gross return "%s, %s, %s" % ( self.sku, self.display_product(), prices_i18n.amount(price), )
class Product(models.Model): product_type = models.ForeignKey(ProductType, related_name='products', on_delete=models.CASCADE) name = models.CharField(max_length=128) description = models.TextField() seo_description = models.CharField(max_length=300, blank=True, null=True, validators=[MaxLengthValidator(300)]) category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE) price = MoneyField(currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2) available_on = models.DateField(blank=True, null=True) is_published = models.BooleanField(default=True) attributes = HStoreField(default={}) updated_at = models.DateTimeField(auto_now=True, null=True) is_featured = models.BooleanField(default=False) objects = ProductQuerySet.as_manager() class Meta: app_label = 'product' permissions = (('view_product', pgettext_lazy('Permission description', 'Can view products')), ('edit_product', pgettext_lazy('Permission description', 'Can edit products')), ('view_properties', pgettext_lazy('Permission description', 'Can view product properties')), ('edit_properties', pgettext_lazy('Permission description', 'Can edit product properties'))) def __iter__(self): if not hasattr(self, '__variants'): setattr(self, '__variants', self.variants.all()) return iter(getattr(self, '__variants')) def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % (class_.__module__, class_.__name__, self.pk, self.name) def __str__(self): return self.name def get_absolute_url(self): return reverse('product:details', kwargs={ 'slug': self.get_slug(), 'product_id': self.id }) def get_slug(self): return slugify(smart_text(unidecode(self.name))) def is_in_stock(self): return any(variant.is_in_stock() for variant in self) def is_available(self): today = datetime.date.today() return self.available_on is None or self.available_on <= today def get_first_image(self): first_image = self.images.first() return first_image.image if first_image else None def get_attribute(self, pk): return self.attributes.get(smart_text(pk)) def set_attribute(self, pk, value_pk): self.attributes[smart_text(pk)] = smart_text(value_pk) def get_price_per_item(self, item, discounts=None): return item.get_price_per_item(discounts) def get_price_range(self, discounts=None): if self.variants.exists(): prices = [ self.get_price_per_item(variant, discounts=discounts) for variant in self ] return TaxedMoneyRange(min(prices), max(prices)) price = TaxedMoney(net=self.price, gross=self.price) discounted_price = calculate_discounted_price(self, price, discounts) return TaxedMoneyRange(start=discounted_price, stop=discounted_price) def get_gross_price_range(self, discounts=None): grosses = [ self.get_price_per_item(variant, discounts=discounts) for variant in self ] if not grosses: return None grosses = sorted(grosses, key=lambda x: x.tax) return TaxedMoneyRange(min(grosses), max(grosses))
class Product(models.Model, ItemRange): product_class = models.ForeignKey(ProductClass, related_name='products', verbose_name=pgettext_lazy( 'Product field', 'product class'), on_delete=models.CASCADE) name = models.CharField(pgettext_lazy('Product field', 'name'), max_length=128) price = models.DecimalField('Price', max_digits=6, decimal_places=2) # description = models.TextField( # verbose_name=pgettext_lazy('Product field', 'description'), blank=True) categories = models.ManyToManyField(Category, verbose_name=pgettext_lazy( 'Product field', 'companies'), related_name='products') is_published = models.BooleanField(pgettext_lazy('Product field', 'is published'), default=True) attributes = HStoreField(pgettext_lazy('Product field', 'attributes'), default={}) updated_at = models.DateTimeField(pgettext_lazy('Product field', 'updated at'), auto_now=True, null=True) objects = ProductManager() class Meta: app_label = 'product' permissions = (('view_product', pgettext_lazy('Permission description', 'Can view products')), ('edit_product', pgettext_lazy('Permission description', 'Can edit products')), ('view_properties', pgettext_lazy('Permission description', 'Can view product properties')), ('edit_properties', pgettext_lazy('Permission description', 'Can edit product properties'))) def __iter__(self): if not hasattr(self, '__variants'): setattr(self, '__variants', self.variants.all()) return iter(getattr(self, '__variants')) def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % (class_.__module__, class_.__name__, self.pk, self.name) def __str__(self): return self.name def get_slug(self): return slugify(smart_text(unidecode(self.name))) def get_first_category(self): for category in self.categories.all(): return category return None def is_available(self): return True def get_first_image(self): first_image = self.images.first() if first_image: return first_image.image return None def get_attribute(self, pk): return self.attributes.get(smart_text(pk)) def set_attribute(self, pk, value_pk): self.attributes[smart_text(pk)] = smart_text(value_pk)
class Contact(models.Model): uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) core_user_uuid = models.UUIDField(blank=True, null=True) customer_id = models.CharField( max_length=32, blank=True, null=True, help_text='ID set by the customer. Must be unique in organization.') first_name = models.CharField(max_length=50, blank=True, help_text='First name', db_index=True) middle_name = models.CharField( max_length=50, blank=True, help_text='Middle name (not common in Germany)') last_name = models.CharField(max_length=50, blank=True, help_text='Surname or family name') title = models.CharField(max_length=16, choices=TITLE_CHOICES, blank=True, null=True, help_text='Choices: {}'.format(", ".join( [kv[0] for kv in TITLE_CHOICES]))) suffix = models.CharField( max_length=50, blank=True, help_text='Suffix for titles like dr., prof., dr. med. etc.') contact_type = models.CharField(max_length=30, choices=CONTACT_TYPE_CHOICES, blank=True, null=True, help_text='Choices: {}'.format(", ".join([ kv[0] for kv in CONTACT_TYPE_CHOICES ]))) customer_type = models.CharField(max_length=30, choices=CUSTOMER_TYPE_CHOICES, blank=True, null=True, help_text='Choices: {}'.format(", ".join([ kv[0] for kv in CUSTOMER_TYPE_CHOICES ]))) company = models.CharField(max_length=100, blank=True, null=True) addresses = ArrayField(HStoreField(), blank=True, null=True, help_text=""" List of 'address' objects with the structure: type (string - Choices: {}), street (string), house_number (string), postal_code: (string), city (string), country (string) """.format(", ".join( [k for k in ADDRESS_TYPE_CHOICES])), validators=[validate_addresses]) siteprofile_uuids = ArrayField(models.UUIDField(), blank=True, null=True, help_text='List of SiteProfile UUIDs') emails = ArrayField(HStoreField(), blank=True, null=True, help_text=""" List of 'email' objects with the structure: type (string - Choices: {}), email (string) """.format(", ".join( [k for k in EMAIL_TYPE_CHOICES])), validators=[validate_emails]) phones = ArrayField(HStoreField(), blank=True, null=True, help_text=""" List of 'phone' objects with the structure: type (string - Choices: {}), number (string) """.format(", ".join( [k for k in PHONE_TYPE_CHOICES])), validators=[validate_phones]) notes = models.TextField(blank=True, null=True) organization_uuid = models.CharField(max_length=36, blank=True, null=True, verbose_name='Organization UUID', db_index=True) workflowlevel1_uuids = ArrayField(models.CharField(max_length=36), help_text='List of Workflowlevel1 UUIDs') workflowlevel2_uuids = ArrayField(models.CharField(max_length=36), blank=True, null=True, help_text='List of Workflowlevel2 UUIDs') def __str__(self): return f"{self.first_name} {self.last_name}" class Meta: indexes = [ GinIndex(fields=['workflowlevel1_uuids']), GinIndex(fields=['workflowlevel2_uuids']) ]
class B2CProduct(ActiveModelMixing, models.Model, IndexedModelMixin): name = models.CharField(max_length=255, blank=False, name=False) categories = models.ManyToManyField(B2CProductCategory, related_name='products') slug = models.SlugField(max_length=255) short_description = models.TextField(null=False) description = models.TextField(blank=False, null=False) image = CustomImageField(upload_to=generate_upload_path, storage=image_storage, sizes=['big', 'small', 'th'], blank=True, null=True, max_length=255) additional_images = ArrayField(ArrayField( models.CharField(max_length=500, blank=True)), blank=True, null=True) company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='b2c_products') keywords = models.CharField(max_length=2048, blank=True, null=False) currency = models.CharField(max_length=255, blank=False, null=True, choices=CURRENCY) cost = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=False) producer = models.ForeignKey(Producer, related_name='b2c_products', verbose_name=_('Producer'), null=True, blank=True) galleries = GenericRelation(Gallery) documents = GenericRelation(Document) show_on_main = models.BooleanField(default=False, db_index=True) is_active = models.BooleanField(default=True) is_deleted = models.BooleanField(default=False, db_index=True) additional_pages = GenericRelation(AdditionalPage) additional_parameters = GenericRelation(AdditionalParameters) metadata = HStoreField() discount_percent = models.FloatField(null=True, blank=True) coupon_discount_percent = models.FloatField(null=True, blank=True) coupon_dates = DateRangeField(null=True, blank=True) extra_params = JSONField(_('Extra param fields'), null=True, blank=True) colors = ArrayField(models.CharField('Colors', max_length=100), blank=True, null=True) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='%(class)s_create_user') updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='%(class)s_update_user') created_at = models.DateTimeField(default=timezone.now, db_index=True) updated_at = models.DateTimeField(auto_now=True) class Meta: index_together = [ ['created_at', 'company'], ] def upload_images(self): from core import tasks params = { 'file': self.image.path, 'sizes': { 'big': { 'box': (500, 500), 'fit': False }, 'small': { 'box': (200, 200), 'fit': False }, 'th': { 'box': (80, 80), 'fit': True } } } tasks.upload_images.delay(params) @property def country(self): return self.company.country @property def sku(self): if self.metadata: return self.metadata.get('stock_keeping_unit', None) return None @staticmethod def get_index_model(**kwargs): from b24online.search_indexes import B2cProductIndex return B2cProductIndex def __str__(self): return self.name def has_perm(self, user): return self.company.has_perm(user) def get_absolute_url(self): return reverse('products:B2CDetail', args=[self.slug, self.pk]) @property def gallery_images(self): model_type = ContentType.objects.get_for_model(self) return GalleryImage.objects.filter(gallery__content_type=model_type, gallery__object_id=self.pk) @property def start_coupon_date(self): if self.coupon_dates: return self.coupon_dates.lower return None @property def end_coupon_date(self): if self.coupon_dates: return self.coupon_dates.upper return None @property def is_coupon(self): return self.start_coupon_date and self.end_coupon_date \ and self.start_coupon_date <= now().date() < self.end_coupon_date @property def has_discount(self): return self.cost and (self.is_coupon or self.discount_percent) def get_discount_price(self): discount_percent = 0 original_price = self.cost or 0 if self.is_coupon: discount_percent = self.coupon_discount_percent elif self.discount_percent: discount_percent = self.discount_percent return original_price - original_price * Decimal( discount_percent) / 100 def get_profit(self): return self.cost - self.get_discount_price() def get_contextmenu_options(self): """ Return extra options for context menu. """ model_type = ContentType.objects.get_for_model(self) if getattr(self, 'pk', None): yield (reverse('questionnaires:list', kwargs={ 'content_type_id': model_type.id, 'item_id': self.pk }), _('Questionnaire')) yield (reverse('products:extra_params_list', kwargs={'item_id': self.pk}), _('Additional_parameters')) def get_extra_params(self): """Return the additional parameters.""" result = [] for field_item in self.extra_params or []: row = {} for field_name, field_value in field_item.items(): if field_name == 'initial': if isinstance(field_value, str): row[field_name] = [('en', field_value)] elif isinstance(field_value, (tuple, list)): row[field_name] = field_value else: row[field_name] = field_value result.append(row) return result
class MyModel(PostgreSQLModel): field = HStoreField(default=dict)
class ProductVariant(models.Model, Item): sku = models.CharField(pgettext_lazy('Product variant field', 'SKU'), max_length=32, unique=True) name = models.CharField(pgettext_lazy('Product variant field', 'variant name'), max_length=100, blank=True) price_override = PriceField(pgettext_lazy('Product variant field', 'price override'), currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2, blank=True, null=True) wholesale_override = PriceField(pgettext_lazy('Product variant field', 'wholesale override'), currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2, blank=True, null=True) product = models.ForeignKey(Product, related_name='variants') attributes = HStoreField(pgettext_lazy('Product variant field', 'attributes'), default={}) images = models.ManyToManyField('ProductImage', through='VariantImage', verbose_name=pgettext_lazy( 'Product variant field', 'images')) low_stock_threshold = models.IntegerField( pgettext_lazy('Product variant field', 'low stock threshold'), validators=[MinValueValidator(0)], null=True, blank=True, default=Decimal(10)) objects = ProductVariantManager() class Meta: app_label = 'product' verbose_name = pgettext_lazy('Product variant model', 'product variant') verbose_name_plural = pgettext_lazy('Product variant model', 'product variants') def __str__(self): return self.name or self.display_variant() def check_quantity(self, quantity): available_quantity = self.get_stock_quantity() if quantity > available_quantity: raise InsufficientStock(self) def get_stock_pk(self): stock_pk = self.stock.all().values('pk') if stock_pk.exists(): for st in stock_pk: stock_pk = st['pk'] else: stock_pk = 0 return stock_pk def get_stock_quantity(self): if not len(self.stock.all()): return 0 return max([stock.quantity_available for stock in self.stock.all()]) def get_price_per_item(self, discounts=None, **kwargs): price = self.price_override or self.product.price price = calculate_discounted_price(self.product, price, discounts, **kwargs) return price def get_wholesale_price_per_item(self, discounts=None, **kwargs): price = self.wholesale_override or self.product.wholesale_price price = calculate_discounted_price(self.product, price, discounts, **kwargs) return price def get_total_price_cost(self): cost = self.get_cost_price() * self.get_stock_quantity() return cost def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse('product:details', kwargs={ 'slug': slug, 'product_id': product_id }) def as_data(self): return { 'product_name': str(self), 'product_id': self.product.pk, 'variant_id': self.pk, 'unit_price': str(self.get_price_per_item().gross) } def is_shipping_required(self): return self.product.product_class.is_shipping_required def is_in_stock(self): return any( [stock.quantity_available > 0 for stock in self.stock.all()]) def get_attribute(self, pk): return self.attributes.get(smart_text(pk)) def set_attribute(self, pk, value_pk): self.attributes[smart_text(pk)] = smart_text(value_pk) def display_variant(self, attributes=None): if attributes is None: attributes = self.product.product_class.variant_attributes.all() values = get_attributes_display_map(self, attributes) if values: return ', '.join([ ' %s' % (smart_text(value)) for (key, value) in six.iteritems(values) ]) else: return smart_text(self.sku) def display_product(self): return '%s (%s)' % (smart_text(self.product), smart_text(self)) def get_first_image(self): return self.product.get_first_image() def select_stockrecord(self, quantity=1): # By default selects stock with lowest cost price stock = filter(lambda stock: stock.quantity_available >= quantity, self.stock.all()) stock = sorted(stock, key=lambda stock: stock.cost_price, reverse=True) if stock: return stock[0] def get_cost_price(self): stock = self.select_stockrecord() if stock: if stock.cost_price: return stock.cost_price else: return 0 else: return 0 def product_category(self): category = self.product.categories.first().name return category
def test_none_allowed_as_value(self): field = HStoreField() self.assertEqual(field.clean({'a': None}, None), {'a': None})
class ProductVariant(models.Model): sku = models.CharField(max_length=32, unique=True) name = models.CharField(max_length=255, blank=True) price_override = MoneyField(currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True) product = models.ForeignKey(Product, related_name='variants', on_delete=models.CASCADE) attributes = HStoreField(default={}, blank=True) images = models.ManyToManyField('ProductImage', through='VariantImage') quantity = models.IntegerField(validators=[MinValueValidator(0)], default=Decimal(1)) quantity_allocated = models.IntegerField(validators=[MinValueValidator(0)], default=Decimal(0)) cost_price = MoneyField(currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True) class Meta: app_label = 'product' def __str__(self): return self.name or self.sku @property def quantity_available(self): return max(self.quantity - self.quantity_allocated, 0) def check_quantity(self, quantity): if quantity > self.quantity_available: raise InsufficientStock(self) @property def base_price(self): return self.price_override or self.product.price def get_price(self, discounts=None, taxes=None): price = calculate_discounted_price(self.product, self.base_price, discounts) if not self.product.charge_taxes: taxes = None tax_rate = (self.product.tax_rate or self.product.product_type.tax_rate) return apply_tax_to_price(taxes, tax_rate, price) def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse('product:details', kwargs={ 'slug': slug, 'product_id': product_id }) def is_shipping_required(self): return self.product.product_type.is_shipping_required def is_in_stock(self): return self.quantity_available > 0 def display_product(self): variant_display = str(self) product_display = ('%s (%s)' % (self.product, variant_display) if variant_display else str(self.product)) return smart_text(product_display) def get_first_image(self): return self.product.get_first_image() def get_ajax_label(self, discounts=None): price = self.get_price(discounts).gross return '%s, %s, %s' % (self.sku, self.display_product(), prices_i18n.amount(price))
def test_model_field_formfield(self): model_field = HStoreField() form_field = model_field.formfield() self.assertIsInstance(form_field, forms.HStoreField)
class Contact(models.Model): """ A contact in RapidPro """ DISPLAY_NAME = "name" DISPLAY_URNS = "urns" DISPLAY_ANON = "uuid" SAVE_GROUPS_ATTR = "__data__groups" org = models.ForeignKey(Org, verbose_name=_("Organization"), related_name="contacts", on_delete=models.PROTECT) uuid = models.CharField(max_length=36, unique=True, null=True) name = models.CharField(verbose_name=_("Full name"), max_length=128, null=True, blank=True, help_text=_("The name of this contact")) groups = models.ManyToManyField(Group, related_name="contacts") fields = HStoreField(null=True) language = models.CharField(max_length=3, verbose_name=_("Language"), null=True, blank=True, help_text=_("Language for this contact")) is_active = models.BooleanField(default=True, help_text="Whether this contact is active") is_blocked = models.BooleanField( default=False, help_text="Whether this contact is blocked") is_stopped = models.BooleanField( default=False, help_text="Whether this contact opted out of receiving messages") is_stub = models.BooleanField( default=False, help_text="Whether this contact is just a stub") suspended_groups = models.ManyToManyField( Group, help_text=_("Groups this contact has been suspended from")) created_on = models.DateTimeField( auto_now_add=True, help_text=_("When this contact was created")) urns = ArrayField(models.CharField(max_length=255), default=list, help_text=_("List of URNs of the format 'scheme:path'")) def __init__(self, *args, **kwargs): if self.SAVE_GROUPS_ATTR in kwargs: setattr(self, self.SAVE_GROUPS_ATTR, kwargs.pop(self.SAVE_GROUPS_ATTR)) super(Contact, self).__init__(*args, **kwargs) @classmethod def get_or_create(cls, org, uuid, name=None): """ Gets an existing contact or creates a stub contact. Used when receiving messages where the contact might not have been synced yet """ with cls.lock(org, uuid): contact = cls.objects.filter(org=org, uuid=uuid).first() if not contact: contact = cls.objects.create(org=org, uuid=uuid, name=name, is_stub=True) return contact @classmethod def get_or_create_from_urn(cls, org, urn, name=None): """ Gets an existing contact or creates a new contact. Used when opening a case without an initial message """ normalized_urn = URN.normalize(urn) contact = cls.objects.filter(urns__contains=[normalized_urn]).first() if not contact: URN.validate(normalized_urn) contact = cls.objects.create(org=org, name=name, urns=[normalized_urn], is_stub=False) org.get_backend().push_contact(org, contact) return contact @classmethod def lock(cls, org, uuid): return get_redis_connection().lock(CONTACT_LOCK_KEY % (org.pk, uuid), timeout=60) def get_display(self): """ Gets the display of this contact. If the site uses anonymous contacts this is generated from the backend UUID. If the display setting is recognised and set then that field is returned, otherwise the name is returned. If no name is set an empty string is returned. """ display_format = getattr(settings, "SITE_CONTACT_DISPLAY", self.DISPLAY_NAME) if display_format == self.DISPLAY_ANON and self.uuid: return self.uuid[:6].upper() elif display_format == self.DISPLAY_URNS and self.urns: _scheme, path = URN.to_parts(self.urns[0]) return path elif display_format == self.DISPLAY_NAME and self.name: return self.name return "---" def get_fields(self, visible=None): fields = self.fields if self.fields else {} if visible: keys = Field.get_all(self.org, visible=True).values_list("key", flat=True) return {k: fields.get(k) for k in keys} else: return fields def get_language(self): if self.language: return { "code": self.language, "name": get_language_name(self.language) } else: return None def prepare_for_case(self): """ Prepares this contact to be put in a case """ if self.is_stub: # pragma: no cover raise ValueError("Can't create a case for a stub contact") # suspend contact from groups while case is open self.suspend_groups() # expire any active flow runs they have self.expire_flows() # labelling task may have picked up messages whilst case was closed. Those now need to be archived. self.archive_messages() def suspend_groups(self): with self.lock(self.org, self.uuid): if self.suspended_groups.all(): # pragma: no cover raise ValueError( "Can't suspend from groups as contact is already suspended from groups" ) cur_groups = list(self.groups.all()) suspend_groups = set(Group.get_suspend_from(self.org)) for group in cur_groups: if group in suspend_groups: self.groups.remove(group) self.suspended_groups.add(group) self.org.get_backend().remove_from_group( self.org, self, group) def restore_groups(self): with self.lock(self.org, self.uuid): for group in list(self.suspended_groups.all()): if not group.is_dynamic: self.groups.add(group) self.org.get_backend().add_to_group(self.org, self, group) self.suspended_groups.remove(group) def expire_flows(self): self.org.get_backend().stop_runs(self.org, self) def archive_messages(self): self.incoming_messages.update(is_archived=True) self.org.get_backend().archive_contact_messages(self.org, self) def release(self): """ Deletes this contact, removing them from any groups they were part of """ self.groups.clear() # mark all messages as inactive and handled self.incoming_messages.update(is_handled=True, is_active=False) self.is_active = False self.save(update_fields=("is_active", )) def as_json(self, full=True): """ Prepares a contact for JSON serialization """ result = {"id": self.pk, "display": self.get_display()} if full: hidden_fields = getattr(settings, "SITE_HIDE_CONTACT_FIELDS", []) result["urns"] = self.urns if "urns" not in hidden_fields else [] result["name"] = self.name if "name" not in hidden_fields else None result["groups"] = [ g.as_json(full=False) for g in self.groups.all() ] result["fields"] = self.get_fields(visible=True) result["language"] = self.get_language() result["blocked"] = self.is_blocked result["stopped"] = self.is_stopped return result def __str__(self): return self.get_display()
class ProductVariant(models.Model): sku = models.CharField(max_length=32, unique=True) name = models.CharField(max_length=100, blank=True) price_override = MoneyField(currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2, blank=True, null=True) product = models.ForeignKey(Product, related_name='variants', on_delete=models.CASCADE) attributes = HStoreField(default={}) images = models.ManyToManyField('ProductImage', through='VariantImage') class Meta: app_label = 'product' def __str__(self): return self.name or self.display_variant_attributes() def check_quantity(self, quantity): total_available_quantity = self.get_stock_quantity() if quantity > total_available_quantity: raise InsufficientStock(self) def get_stock_quantity(self): return sum([stock.quantity_available for stock in self.stock.all()]) def get_price_per_item(self, discounts=None): price = self.price_override or self.product.price price = TaxedMoney(net=price, gross=price) price = calculate_discounted_price(self.product, price, discounts) return price def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse('product:details', kwargs={ 'slug': slug, 'product_id': product_id }) def as_data(self): return { 'product_name': str(self), 'product_id': self.product.pk, 'variant_id': self.pk, 'unit_price': str(self.get_price_per_item().gross) } def is_shipping_required(self): return self.product.product_type.is_shipping_required def is_in_stock(self): return any( [stock.quantity_available > 0 for stock in self.stock.all()]) def display_variant_attributes(self, attributes=None): if attributes is None: attributes = self.product.product_type.variant_attributes.all() values = get_attributes_display_map(self, attributes) if values: return ', '.join([ '%s: %s' % (smart_text(attributes.get(id=int(key))), smart_text(value)) for (key, value) in values.items() ]) return '' def display_product(self): variant_display = str(self) product_display = ('%s (%s)' % (self.product, variant_display) if variant_display else str(self.product)) return smart_text(product_display) def get_first_image(self): return self.product.get_first_image() def select_stockrecord(self, quantity=1): # By default selects stock with lowest cost price. If stock cost price # is None we assume price equal to zero to allow sorting. stock = [ stock_item for stock_item in self.stock.all() if stock_item.quantity_available >= quantity ] zero_price = Money(0, settings.DEFAULT_CURRENCY) stock = sorted(stock, key=(lambda s: s.cost_price or zero_price), reverse=False) if stock: return stock[0] return None def get_cost_price(self): stock = self.select_stockrecord() if stock: return stock.cost_price return None
class Listing(SlugMixin, AbstractProduct): FORCE_SLUG_REGENERATION = False SOCIAL_NETWORKS = ['Facebook', 'Twitter', 'Google', 'Instagram', 'Vimeo', 'YouTube', 'LinkedIn', 'Dribbble', 'Skype', 'Foursquare', 'Behance'] # TODO: move to settings PRICE_UNITS = [ ('ENTRY', _('entry')), # tickets ('HOUR', _('hour')), # parking ('DAY', _('day')), # vehicle per day ('NIGHT', _('night')), # room unit per night ] # definition title = models.CharField(_('title'), max_length=100) slug = models.SlugField(unique=True, max_length=SlugMixin.MAX_SLUG_LENGTH, blank=True) description = models.TextField(_('description'), blank=True) # management author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('author')) published = models.BooleanField(_('published'), default=True) hidden = models.BooleanField(_('hidden'), default=False) promoted = models.BooleanField(_('promoted'), default=False) rank = models.PositiveSmallIntegerField(_('rank'), default=1) # specification categories = models.ManyToManyField(to=Category, verbose_name=_('categories'), blank=True, related_name='listings_of_category') features = models.ManyToManyField(to=Feature, verbose_name=_('features'), blank=True, related_name='listings_with_features') # price price_starts_at = models.BooleanField(_('price starts at'), default=False) # price = models.DecimalField(_('price'), help_text=inventor_settings.CURRENCY, max_digits=10, decimal_places=2, db_index=True, validators=[MinValueValidator(0)], # blank=True, null=True, default=None) price_unit = models.CharField(_('price unit'), choices=PRICE_UNITS, max_length=5, blank=True) price_per_person = models.BooleanField(_('price per person'), default=False) # location locality = models.ForeignKey(Locality, on_delete=models.SET_NULL, blank=True, null=True, default=None) address = models.TextField(_('address'), help_text=_('street, postcode, city'), max_length=500, blank=True) # street = models.CharField(_('street'), max_length=200, blank=True) # postcode = models.CharField(_('postcode'), max_length=30, blank=True) # city = models.CharField(_('city'), max_length=50, blank=True) country = CountryField(verbose_name=_('country'), blank=True, db_index=True) point = models.PointField(_('point'), blank=True, null=True, default=None, db_index=True) # previews image = thumbnail.ImageField( verbose_name=_('image'), help_text=_('photo or image'), max_length=1024, upload_to='images', blank=True ) banner = models.ImageField( verbose_name=_('banner'), help_text=_('photo or image'), max_length=1024 * 5, upload_to='banners', blank=True ) # contact information person = models.CharField(_('person'), max_length=100, blank=True) phone = models.CharField(_('phone'), max_length=40, blank=True) email = models.EmailField(_('email'), blank=True) website = models.URLField(_('website'), max_length=400, blank=True) # social social_networks = HStoreField(verbose_name=_('social networks'), blank=True, default=dict) # relations # comments = GenericRelation(get_comment_model(), content_type_field='content_type', object_id_field='object_pk', # related_query_name='comment') supplies = GenericRelation('commerce.Supply', content_type_field='content_type', object_id_field='object_id', related_query_name='product') created = models.DateTimeField(_('created'), auto_now_add=True) modified = models.DateTimeField(_('modified'), auto_now=True) i18n = TranslationField(fields=('title', 'slug', 'description')) objects = MultilingualManager.from_queryset(ListingQuerySet)() class Meta: verbose_name = _('listing') verbose_name_plural = _('listings') ordering = ('title',) db_table = 'listings_general' indexes = [GinIndex(fields=["i18n"]), ] def __str__(self): return self.title_i18n def get_absolute_url(self): return reverse('listings:listing_detail', args=(self.slug_i18n,)) def get_update_url(self): return reverse('listings:listing_update', args=(self.pk,)) @property def full_address(self): return '{}, {}'.format(self.address, self.country).strip(', ') def get_price_display(self): if not self.price: return '' price = str(self.price) if price.endswith('.00'): price = price[:-3] if inventor_settings.CURRENCY_AFTER_AMOUNT: price_display = '{} {}'.format(price, inventor_settings.CURRENCY_SYMBOL) # example: 10 € else: price_display = '{}{}'.format(inventor_settings.CURRENCY_SYMBOL, price) # example: $10 if self.price_starts_at: price_display = '{} {}'.format(_('starts at'), price_display) if self.price_unit: price_display = '{} / {}'.format(price_display, self.get_price_unit_display()) return price_display @cached_property def rating(self): avg_rating = self.comments.aggregate(Avg('rating')) # TODO: rating subjects: # - Facilities / Amenities # - Cleanliness # - Comfort # - Location # - Services? rating = avg_rating['rating__avg'] if rating: rating = round(rating, 2) return rating def delete(self, **kwargs): """ Deletes file before deleting instance """ self.delete_banner() super().delete(**kwargs) def delete_banner(self): """ Deletes image file """ try: os.remove(self.banner.path) except ValueError: pass except IOError: pass except OSError: pass @property def listing_class(self): return self.__class__ @property def listing_class_name(self): return self.__class__.__name__ def get_listing_type_display(self): # return self.listing_class._meta.verbose_name return self.get_real_instance().listing_class._meta.verbose_name def get_listing_type_display_plural(self): # return self.listing_class._meta.verbose_name_plural return self.get_real_instance().listing_class._meta.verbose_name_plural @cached_property def all_images(self): return Photo.objects.filter(album__listing__pk=self.pk) @classmethod def get_list_url_name(cls): url_name = f'{cls.__name__.lower()}_list' return f'{url_name}' @classmethod def get_list_url(cls): url_name = cls.get_list_url_name() return reverse(f'listings:{url_name}') # https://stackoverflow.com/questions/21063078/convert-a-subclass-model-instance-to-another-subclass-model-instance-in-django def convert(self, to_type): instance = self # print(f'[{instance.id}]\t {instance}') # create new instance with same parent ID new_instance = to_type(listing_ptr_id=instance.id) # update parent fields new_instance.__dict__.update(instance.__dict__) # delete the subclass while keeping the parent instance.delete(keep_parents=True) # save new instance new_instance.save() def get_real_instance(self): """ get object child instance """ def get_subclasses(cls): subclasses = cls.__subclasses__() result = [] for subclass in subclasses: if not subclass._meta.abstract: result.append(subclass) else: result += get_subclasses(subclass) return result if hasattr(self, '_real_instance'): # try looking in our cache return self._real_instance subclasses = get_subclasses(self.__class__) if not subclasses: # already real_instance self._real_instance = self return self._real_instance else: subclasses_names = [cls.__name__.lower() for cls in subclasses] for subcls_name in subclasses_names: if hasattr(self, subcls_name): return getattr(self, subcls_name, self) return self
class Preprocessed_Data(models.Model): pp_recording = models.ForeignKey(Preprocessed_Recording, on_delete=models.CASCADE) store = HStoreField()