class Answer(TreeNode): objects: AnswerQuerySet = AnswerQuerySet.as_manager() created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(null=True, blank=True, db_index=True) slug = models.UUIDField(db_index=True, unique=True, default=uuid.uuid4) question = models.ForeignKey('homework.Question', on_delete=models.CASCADE) author = models.ForeignKey('users.User', on_delete=models.CASCADE) text = MarkdownxField() class Meta: verbose_name = _('Homework answer') verbose_name_plural = _('Homework answers') ordering = ['created'] permissions = [ ('see_all_answers', _('May see answers from every user')), ] def save(self, *args, **kwargs): if self.pk: self.modified = timezone.now() return super().save(*args, **kwargs)
class Answer(TreeNode): objects: AnswerQuerySet = AnswerQuerySet.as_manager() created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True, db_index=True) slug = models.UUIDField(db_index=True, unique=True, default=uuid.uuid4) question = models.ForeignKey('homework.Question', on_delete=models.CASCADE) author = models.ForeignKey('users.User', on_delete=models.CASCADE) do_not_crosscheck = models.BooleanField(_('Exclude from cross-checking'), default=False) text = MarkdownxField() class Meta: verbose_name = _('Homework answer') verbose_name_plural = _('Homework answers') ordering = ['created'] permissions = [ ('see_all_answers', _('May see answers from every user')), ] def get_root_answer(self): ancesorts = self.ancestors() if ancesorts.count(): return ancesorts[0] return self def get_absolute_url(self): root = self.get_root_answer() url = urljoin(settings.FRONTEND_URL, f'homework/answers/{root.slug}/') if root != self: url = f'{url}#{self.slug}' # append hash with current answer id return url def get_purchased_course(self): latest_purchase = Order.objects.paid().filter( user=self.author, course__in=self.question.courses.all()).order_by('-paid').first() if latest_purchase: return latest_purchase.course def get_first_level_descendants(self): return self.descendants().filter(parent=self.id) def __str__(self): LENGTH = 30 text = self.text[:LENGTH] if len(text) == LENGTH: text += '...' return text
class AnswerAccessLogEntry(TimestampedModel): objects: AnswerAccessLogEntryQuerySet = AnswerAccessLogEntryQuerySet.as_manager() answer = models.ForeignKey('homework.Answer', on_delete=models.CASCADE) user = models.ForeignKey('users.User', on_delete=models.CASCADE) class Meta: indexes = [ Index(fields=['answer', 'user']), ] constraints = [ UniqueConstraint(fields=['answer', 'user'], name='unique_user_and_answer'), ]
class AnswerCrossCheck(TimestampedModel): answer = models.ForeignKey('homework.Answer', on_delete=models.CASCADE) checker = models.ForeignKey('users.User', on_delete=models.CASCADE) class Meta: indexes = [ Index(fields=['answer', 'checker']), ] constraints = [ UniqueConstraint(fields=['answer', 'checker'], name='unique_checker_and_answer'), ]
class AnswerCrossCheck(TimestampedModel): answer = models.ForeignKey('homework.Answer', on_delete=models.CASCADE) checker = models.ForeignKey('users.User', on_delete=models.CASCADE) class Meta: indexes = [ Index(fields=['answer', 'checker']), ] constraints = [ UniqueConstraint(fields=['answer', 'checker'], name='unique_checker_and_answer'), ] def is_checked(self): return self.answer.descendants().filter(author=self.checker).exists()
class Study(TimestampedModel): student = models.ForeignKey('users.User', on_delete=models.CASCADE) course = models.ForeignKey('products.Course', on_delete=models.CASCADE) order = models.OneToOneField('orders.Order', on_delete=models.CASCADE) class Meta: indexes = [ Index(fields=['student', 'course']), ] constraints = [ UniqueConstraint(fields=['student', 'course'], name='unique_student_course_study'), ] def __str__(self): return f'{self.student} / {self.course}'
class Employee(Timestamped, AppModel): branch = models.ForeignKey( to='branches.Branch', related_name='employees', on_delete=models.SET_NULL, null=True, blank=True, ) first_name = models.CharField( max_length=255, db_index=True, ) last_name = models.CharField( max_length=255, db_index=True, ) position = models.CharField( max_length=255, db_index=True, ) objects = EmployeeQuerySet.as_manager() class Meta: ordering = ['-id'] indexes = [ GinIndex(name='employee_fname_gin_index', fields=['first_name'], opclasses=['gin_trgm_ops']), GinIndex(name='employee_lname_gin_index', fields=['last_name'], opclasses=['gin_trgm_ops']), ] @property def name(self) -> str: return ' '.join([name for name in [self.first_name, self.last_name] if name])
class Record(Shippable): course = models.ForeignKey(Course, on_delete=models.CASCADE) s3_object_id = models.CharField(max_length=512) template_id = models.CharField( _('Postmark template_id'), max_length=256, blank=True, null=True, help_text=_('Leave it blank for the default template')) class Meta: ordering = ['-id'] verbose_name = _('Record') verbose_name_plural = _('Records') @property def name_genitive(self): return self.course.name_genitive def get_url(self, expires: int = 30 * 24 * 60 * 60): return AppS3().get_presigned_url(self.s3_object_id, expires=expires) def __str__(self): return f'Запись {self.name_genitive}' def get_absolute_url(self): return self.course.get_absolute_url()
class Diploma(TimestampedModel): class Languages(models.TextChoices): RU = 'RU', _('Russian') EN = 'EN', _('English') objects: DiplomaQuerySet = DiplomaQuerySet.as_manager() study = models.ForeignKey('studying.Study', on_delete=models.CASCADE) slug = models.CharField(max_length=32, db_index=True, unique=True, default=shortuuid.uuid) language = models.CharField(max_length=3, choices=Languages.choices, db_index=True) image = models.ImageField(upload_to=RandomFileName('diplomas')) class Meta: constraints = [ models.UniqueConstraint(fields=['study', 'language'], name='unique_study'), ] indexes = [ models.Index(fields=['study', 'language']), ] ordering = ['-id'] permissions = [ ('access_all_diplomas', _('May access diplomas of all students')), ] verbose_name = _('Diploma') verbose_name_plural = _('Diplomas') def get_other_languages(self) -> DiplomaQuerySet: return self.__class__.objects.filter(study=self.study).exclude( pk=self.pk) def get_absolute_url(self) -> str: return urljoin(settings.DIPLOMA_FRONTEND_URL, f'/{self.slug}/') def send_to_student(self): send_mail.delay( to=self.study.student.email, template_id='new-diploma', ctx=dict( course_name=self.study.course.full_name, diploma_url=self.get_absolute_url(), ), disable_antispam=True, )
class Shippable(TimestampedModel): """Add this to every shippable item""" name = models.CharField(max_length=255) name_receipt = models.CharField(_('Name for receipts'), max_length=255, help_text='«посещение мастер-класса по TDD» или «Доступ к записи курсов кройки и шитья»') full_name = models.CharField( _('Full name for letters'), max_length=255, help_text='Билет на мастер-класс о TDD или «запись курсов кройки и шитья»', ) slug = models.SlugField() price = models.DecimalField(max_digits=8, decimal_places=2) old_price = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True) tinkoff_credit_promo_code = models.CharField(_('Fixed promo code for tinkoff credit'), max_length=64, blank=True, help_text=_('Used in tinkoff credit only')) group = models.ForeignKey('products.Group', verbose_name=_('Analytical group'), null=True, blank=True, on_delete=models.SET_NULL) class Meta: abstract = True def get_price_display(self): return format_price(self.price) def get_old_price_display(self): return format_price(self.old_price) def get_formatted_price_display(self): return format_old_price(self.old_price, self.price) def ship(self, to: User, order: Optional[Order] = None): return ShipmentFactory.ship(self, to=to, order=order) def unship(self, order: Order): return ShipmentFactory.unship(order=order) def get_price(self, promocode=None) -> Decimal: promocode = apps.get_model('orders.PromoCode').objects.get_or_nothing(name=promocode) if promocode is not None: return promocode.apply(self) return self.price def get_template_id(self): """Get custom per-item template_id""" if not hasattr(self, 'template_id'): return if self.template_id is not None and len(self.template_id): return self.template_id
class PasswordlessAuthToken(TimestampedModel): objects = PasswordlessAuthTokenQuerySet.as_manager() user = models.ForeignKey('users.User', on_delete=models.CASCADE) token = models.UUIDField(default=uuid.uuid4, unique=True, db_index=True) expires = models.DateTimeField(default=default_expiration) used = models.DateTimeField(null=True) def get_absolute_url(self): return urljoin(settings.FRONTEND_URL, '/'.join(['auth', 'passwordless', str(self.token), ''])) def mark_as_used(self): if not settings.DANGEROUSLY_MAKE_ONE_TIME_PASSWORDLESS_TOKEN_MULTI_PASS: self.used = timezone.now() self.save()
class Migration(migrations.Migration): dependencies = [ ('models', '0001_initial'), ] operations = [ migrations.CreateModel( name='Interface', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('local_interface', models.CharField(default=b'', max_length=30)), ('remote_interface', models.CharField(default=b'', max_length=30, null=True)), ('status', models.CharField(choices=[ (app.models.models.Status(b'Down'), b'Down'), (app.models.models.Status(b'Shutdown'), b'Shutdown'), (app.models.models.Status(b'Up'), b'Up') ], default=app.models.models.Status(b'Down'), max_length=50)), ('connected_interface', models.CharField(default=b'', max_length=30)), ('connected_router', models.CharField(default=b'', max_length=30)), ('card', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='card', to='models.Card')), ], ), ]
class Token(TimestampedModel): objects = TokenQuerySet.as_manager() # type: TokenQuerySet token = models.CharField(max_length=36, default=uuid.uuid4, unique=True, db_index=True) record = models.ForeignKey('courses.Record', on_delete=models.CASCADE) expires = models.DateTimeField(blank=True, null=True) class Meta: verbose_name = _('Onetime token') verbose_name_plural = _('Onetime tokens') def __str__(self): return str(self.token) def download(self): self.expires = timezone.now() + EXPIRATION_TIME self.save() return self.record.get_url()
class Batch(TimestampedModel): recipe = models.ForeignKey("lab.recipe", verbose_name=_("Recipe"), on_delete=models.PROTECT) volume = models.DecimalField(_("Volume, m3"), max_digits=10, decimal_places=3, default=0) cement_weight = models.DecimalField(_("Cement, kg"), max_digits=10, decimal_places=3, default=0) sand_weight = models.DecimalField(_("Sand, kg"), max_digits=10, decimal_places=3, default=0) gravel_weight = models.DecimalField(_("Gravel, kg"), max_digits=10, decimal_places=3, default=0) water_weight = models.DecimalField(_("Water, kg"), max_digits=10, decimal_places=3, default=0) admixture_weight = models.DecimalField(_("Admixture, kg"), max_digits=10, decimal_places=3, default=0) class Meta: verbose_name = _("Batch") verbose_name_plural = _("Batches") def __str__(self): return f"{self.recipe.name}: {self.volume} м3"
class Order(TimestampedModel): objects = OrderQuerySet.as_manager() # type: OrderQuerySet user = models.ForeignKey('users.User', on_delete=models.PROTECT) price = models.DecimalField(max_digits=9, decimal_places=2) promocode = models.ForeignKey('orders.PromoCode', verbose_name=_('Promo Code'), blank=True, null=True, on_delete=models.PROTECT) paid = models.DateTimeField( _('Date when order got paid'), null=True, blank=True, help_text=_('If set during creation, order automaticaly gets shipped'), ) shipped = models.DateTimeField(_('Date when order was shipped'), null=True, blank=True) course = ItemField('courses.Course', null=True, blank=True, on_delete=models.PROTECT) record = ItemField('courses.Record', null=True, blank=True, on_delete=models.PROTECT) bundle = ItemField('courses.Bundle', null=True, blank=True, on_delete=models.PROTECT) class Meta: ordering = ['-id'] verbose_name = _('Order') verbose_name_plural = _('Orders') def __str__(self): return f'Order #{self.pk}' @property def item(self): """Find the attached item. Simple replacement for ContentType framework """ for field in self.__class__._meta.get_fields(): if getattr(field, '_is_item', False): if getattr(self, f'{field.name}_id', None) is not None: return getattr(self, field.name) @classmethod def _iterate_items(cls) -> Iterable[models.fields.Field]: for field in cls._meta.get_fields(): if getattr(field, '_is_item', False): yield field @classmethod def get_item_foreignkey(cls, item) -> Optional[models.fields.Field]: """ Given an item model, returns the ForeignKey to it""" for field in cls._iterate_items(): if field.related_model == item.__class__: return field.name def reset_items(self): for field in self._iterate_items(): setattr(self, field.name, None) def set_item(self, item): foreign_key = self.__class__.get_item_foreignkey(item) if foreign_key is not None: self.reset_items() setattr(self, foreign_key, item) return raise UnknownItemException('There is not foreignKey for {}'.format( item.__class__)) def set_paid(self): is_already_paid = self.paid is not None self.paid = timezone.now() self.save() if not is_already_paid and self.item is not None: self.ship() def ship(self): """Ship the order. Better call it asynchronously""" self.item.ship(to=self.user) self.shipped = timezone.now() self.save() order_got_shipped.send( sender=self.__class__, order=self, )
class LeadCampaignLogEntry(TimestampedModel): campaign = models.ForeignKey(EmailLeadMagnetCampaign, on_delete=models.CASCADE) user = models.ForeignKey('users.User', on_delete=models.CASCADE)
class Order(TimestampedModel): objects = OrderQuerySet.as_manager() # type: OrderQuerySet user = models.ForeignKey('users.User', verbose_name=_('User'), on_delete=models.PROTECT) price = models.DecimalField(_('Price'), max_digits=9, decimal_places=2) promocode = models.ForeignKey('orders.PromoCode', verbose_name=_('Promo Code'), blank=True, null=True, on_delete=models.PROTECT) paid = models.DateTimeField( _('Date when order got paid'), null=True, blank=True, help_text=_('If set during creation, order automaticaly gets shipped'), ) shipped = models.DateTimeField(_('Date when order was shipped'), null=True, blank=True) desired_bank = models.CharField(_('User-requested bank string'), blank=True, max_length=32) course = ItemField(to='products.Course', verbose_name=_('Course'), null=True, blank=True, on_delete=models.PROTECT) record = ItemField(to='products.Record', verbose_name=_('Record'), null=True, blank=True, on_delete=models.PROTECT) bundle = ItemField(to='products.Bundle', verbose_name=_('Bundle'), null=True, blank=True, on_delete=models.PROTECT) giver = models.ForeignKey('users.User', verbose_name=_('Giver'), null=True, blank=True, on_delete=models.SET_NULL, related_name='created_gifts') desired_shipment_date = models.DateTimeField( _('Date when the gift should be shipped'), null=True, blank=True) gift_message = models.TextField(_('Gift message'), default='', blank=True) notification_to_giver_is_sent = models.BooleanField(default=False) class Meta: ordering = ['-id'] verbose_name = _('Order') verbose_name_plural = _('Orders') def __str__(self): return f'Order #{self.pk}' @property def item(self): """Find the attached item. Simple replacement for ContentType framework """ for field in self.__class__._meta.get_fields(): if getattr(field, '_is_item', False): if getattr(self, f'{field.name}_id', None) is not None: return getattr(self, field.name) @classmethod def _iterate_items(cls) -> Iterable[models.fields.Field]: for field in cls._meta.get_fields(): if getattr(field, '_is_item', False): yield field @classmethod def get_item_foreignkey(cls, item) -> Optional[models.fields.Field]: """ Given an item model, returns the ForeignKey to it""" for field in cls._iterate_items(): if field.related_model == item.__class__: return field.name def reset_items(self): for field in self._iterate_items(): setattr(self, field.name, None) def set_item(self, item): foreign_key = self.__class__.get_item_foreignkey(item) if foreign_key is not None: self.reset_items() setattr(self, foreign_key, item) return raise UnknownItemException('There is not foreignKey for {}'.format( item.__class__)) def set_paid(self, silent=False): from orders.services.order_is_paid_setter import Griphook Griphook(self, silent=silent)() def ship(self, silent: bool = False): """Ship the order. Better call it asynchronously""" from orders.services.order_shipper import Pigwidgeon Pigwidgeon(self, silent=silent)()