class Photo(models.Model): album = models.ForeignKey(Album, related_name='photos', on_delete=models.CASCADE) name = models.CharField(max_length=50) position = PositionField(collection='album', default=0) def __unicode__(self): return self.name
class Task(models.Model): """ Base class for lessons/exercises - ordered items within a sub-unit """ sub_unit = models.ForeignKey(SubUnit) title = models.CharField(max_length=100) position = PositionField(collection='sub_unit', parent_link='task_ptr')
class Unit(models.Model): """Represents a PDF of a unit, with problems and solutions""" group = models.ForeignKey(UnitGroup, null=True, on_delete=models.CASCADE, help_text="The group that this unit belongs to") code = models.CharField( max_length=255, help_text="The version code for the handout, like 'ZGX'") prob_url = models.CharField(max_length=255, help_text="The URL for the problems handout", blank=True) soln_url = models.CharField(max_length=255, help_text="The URL for the solutions handout", blank=True) position = PositionField( help_text="The ordering of the relative handouts to each other.") def __str__(self): if self.group is not None: return self.group.name + " [" + self.code + "]" return "-" + " [" + self.code + "]" class Meta: unique_together = ('group', 'code') ordering = ('position', ) @property def list_display_position(self): return self.position
class ForumPost(models.Model): old_id = models.PositiveIntegerField(null=True, db_index=True) author = models.ForeignKey( to='users.User', related_name='forum_posts', null=True, on_delete=models.PROTECT, ) body = models.TextField() created_at = models.DateTimeField(auto_now_add=True) modified = models.BooleanField(default=False) modified_at = models.DateTimeField(auto_now=True, null=True, editable=False) modified_count = models.PositiveIntegerField(default=0, editable=False) modified_by = models.ForeignKey( to='users.User', related_name='modified_posts', null=True, on_delete=models.SET_NULL, editable=False, ) thread = models.ForeignKey( to='forums.ForumThread', related_name='posts', on_delete=models.CASCADE, ) position = PositionField(collection='thread', editable=False) objects = ForumPostQuerySet.as_manager() class Meta: ordering = ['created_at'] get_latest_by = ['created_at'] def __str__(self): return 'Forum post by {author} in thread {thread}'.format( author=self.author, thread=self.thread, ) def get_absolute_url(self): return '{thread_url}#{post_id}'.format( thread_url=self.thread.get_absolute_url(), post_id=self.id, ) def save(self, *args, **kwargs): if self.pk: if self.modified: self.modified_count = F('modified_count') + 1 super(ForumPost, self).save(*args, **kwargs) if self.pk: # As we use F expression, its not possible to know modified_count until refresh from db self.refresh_from_db()
class SizeGroup(models.Model): name = models.CharField(max_length=30, unique=True) position = PositionField() def __str__(self): return self.name class Meta: ordering = ['position']
class Example(models.Model): ml_model = models.ForeignKey(MLModel, on_delete=models.CASCADE) position = PositionField(collection="ml_model") image = models.ImageField(upload_to='examples/') description = models.TextField(null=True, blank=True) source = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return "{} - Example {}".format(self.ml_model.name, self.position)
class Layer(models.Model): ml_model = models.ForeignKey(MLModel, on_delete=models.CASCADE) position = PositionField(collection="ml_model") name = models.CharField(max_length=100) layer_type = models.CharField(max_length=100, default="none") properties = JSONField(null=True, blank=True) def __str__(self): return "{}: {}".format(self.ml_model.name, self.name)
class Size(models.Model): name = models.CharField(max_length=30) group = models.ForeignKey(SizeGroup, related_name='sizes', on_delete=models.CASCADE) position = PositionField(collection='group') def __str__(self): return "{} {}".format(str(self.group), self.name) class Meta: ordering = ['position']
class Product(models.Model): name = models.CharField(max_length=30, unique=True) price = models.DecimalField(max_digits=10, decimal_places=2) position = PositionField() def __str__(self): return self.name class Meta: ordering = ['position']
class Lesson(models.Model): STATES = ( ('draft', _('Draft')), ('listed', _('Listed')), ('published', _('Published')), ) course = models.ForeignKey(Course, verbose_name=_('Course'), related_name='lessons') desc = models.TextField(_('Description')) name = models.CharField(_('Name'), max_length=255) notes = models.TextField(_('Notes'), default="", blank=True) position = PositionField(collection='course', default=0) slug = AutoSlugField(_('Slug'), populate_from='name', max_length=255, editable=False, unique=True) status = models.CharField(_('Status'), choices=STATES, default=STATES[0][0], max_length=64) class Meta: verbose_name = _('Lesson') verbose_name_plural = _('Lessons') ordering = ['position'] def __unicode__(self): return self.name def thumbnail(self): try: first_vid_unit = self.units.exclude( video=None).order_by('position')[0] thumbnail = 'http://i1.ytimg.com/vi/' + first_vid_unit.video.youtube_id + '/hqdefault.jpg' return thumbnail except IndexError: return staticfiles_storage.url('img/lesson-default.png') def activity_count(self): # FIXME verify activies app dependency in core app is acceptable, refs to #428 from activities.models import Activity return Activity.objects.filter(unit__lesson=self).count() def unit_count(self): return self.units.all().count() def video_count(self): return self.units.exclude(video=None).count() def is_ready(self): return self.status == 'published' and self.units.exists()
class Item(CommonInfo): class Meta(CommonInfo.Meta): verbose_name = _("Item") verbose_name_plural = _("Items") TYPE_CHOICES = ((_('Men'), _('Men')), (_('Women'), _('Women'))) title = models.CharField(max_length=256, verbose_name=_('name')) title_image = models.ImageField(upload_to='catalog/products', verbose_name=_('title image')) description = models.TextField(blank=True, null=True, verbose_name=_('description')) slug = models.SlugField(blank=True, null=True) type = models.CharField(choices=TYPE_CHOICES, max_length=7, default=_('Women'), verbose_name=_('type')) category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name=_('сategory')) collection = models.ForeignKey(Collection, null=True, on_delete=models.CASCADE, verbose_name=_('сollection')) recommended_items = models.ManyToManyField('self', blank=True, verbose_name=_('recommended')) sales = models.IntegerField(verbose_name=_('sales, %'), blank=True, null=True) best_seller = models.BooleanField(default=False) is_not_leather_chain = models.BooleanField(default=True) position = PositionField() is_available = models.BooleanField(default=True) def save(self, *args, **kwargs): if self.sales: for item in self.attributes_set.all(): item.sales_price = item.price - item.price * self.sales / 100 item.save() else: for item in self.attributes_set.all(): item.sales_price = None item.save() super().save(*args, **kwargs) def get_first_attribute(self): return Attributes.objects.filter(item=self).first() def __str__(self): return "{}-{}-{}-{}".format(self.collection, self.category, self.title, self.position)
class Sponsor(models.Model): website = models.ForeignKey(Website, null=False, blank=False) name = models.CharField(max_length=200, null=False, blank=False) logo = models.ImageField(upload_to="event/sponsors/", null=False, blank=False) description = models.TextField(null=True, blank=True) url = models.URLField(null=False, blank=False) order = PositionField() def __unicode__(self): return self.name
class Noticeship(models.Model): zone = models.ForeignKey(Zone) notice = models.ForeignKey(Notice) position = PositionField(_('Position'), collection='zone') class Meta: verbose_name = 'Noticeship' verbose_name_plural = _('Noticeships') ordering = ('position', ) def __unicode__(self): return self.notice.title
class NewsImage(models.Model): """Image Model""" entry = models.ForeignKey(Entry, related_name="gallery") image = models.ImageField('image', upload_to='news/images') caption = models.TextField(blank=True) position = PositionField(collection="entry") class Meta: ordering = ["entry", "position"] def __unicode__(self): return self.caption
class Board(TimeStampedModel): company = models.ForeignKey('nc_core.Company', verbose_name=_('company'), related_name='boards', blank=True, null=True) title = models.CharField(_("title"), max_length=512, blank=True) position = PositionField(_("position"), unique_for_fields=('company', ), default=0) @property def column_workflow_start(self): """ Получение первой колонки на доске """ columns = self.columns.filter(type=Column.TYPE_CHOICES.FIRST) if columns.exists(): return columns.first() else: return self.columns.filter( type=Column.TYPE_CHOICES.REGULAR).order_by('position').first() @property def column_workflow_end(self): """ Получение последней колонки на доске """ columns = self.columns.filter(type=Column.TYPE_CHOICES.LAST) if columns.exists(): return columns.last() else: return self.columns.filter(type=Column.TYPE_CHOICES.REGULAR ).order_by('-position').first() class Meta(object): verbose_name = _('board') verbose_name_plural = _('boards') ordering = ('position', ) def __str__(self): return self.title def delete(self, using=None, keep_parents=False): from . import Card # Карточки удаляемой доски уходят в корзину Card.objects.filter(column__board=self).update( status=Card.STATUS_CHOICES.BASKET, basket_date=timezone.now()) return super().delete(using=using, keep_parents=keep_parents)
class CheckListItem(models.Model): """ Checklist Item model """ checklist = models.ForeignKey('CheckList', on_delete=models.CASCADE, related_name='items') item = models.TextField(max_length=1000) is_checked = models.BooleanField(default=False) position = PositionField(collection='checklist') importance = models.IntegerField(default=0) def __unicode__(self): if self.is_checked: return "(done) %s" % self.item return self.item
class Category(BaseModel): """Category of submitted thesis.""" title = models.CharField( verbose_name=_('Title'), max_length=128, ) order = PositionField(verbose_name=_('Order'), ) class Meta: ordering = ['order', 'title'] verbose_name = _('Category') verbose_name_plural = _('Categories') def __str__(self): return self.title
class Stage(models.Model): """Life Stage""" name = models.CharField(max_length=40) slug = models.SlugField(max_length=40) image = models.ImageField(upload_to="stages", null=True) position = PositionField() def __str__(self): """Set admin label""" return self.name class Meta: """Set verbose names""" verbose_name = "Stage" verbose_name_plural = "Stages"
class DynamicFormField(models.Model): """ Single field in a dynamic form """ name = models.CharField(max_length=100, help_text=ugettext_lazy( "Name of the field, it will be displayed as " "label for this question")) field_type = models.CharField( max_length=100, choices=get_field_type_choices(), help_text=ugettext("Type of data this field stores")) required = models.BooleanField(default=True) form = models.ForeignKey(DynamicForm, related_name='fields') """ Form for which this object """ additional_data = hstore.DictionaryField(blank=True, null=False, default=lambda: {}) """ Dictionary of additional data, contents are defined by: :attr:`DynamicFormField.field_type` """ position = PositionField(collection='form') objects = hstore.HStoreManager() def get_django_field(self): return self.dynamic_field.load_field(self) @property def dynamic_field(self): """ Returns instance of :class:`DynamicFieldController`, object that encapsulates behaviour of this field. It is depenedent on `DynamicFormField.field_type` :return: :class:`DynamicFieldController`, :rtype: :class:`DynamicFieldController`, """ return get_field(self.field_type) class Meta: ordering = ['position']
class Collectable(models.Model): original_work = models.ForeignKey(Work) comments = models.TextField(u'הערות') position = PositionField(collection='user', verbose_name=u'מיקום') user = models.ForeignKey(User) class Meta: ordering = ['position'] verbose_name = u'פריט אוסף' def move_up(self): self.position -= 1 self.save() def move_down(self): self.position += 1 self.save()
class Employee(ChangeTrackerMixin): first_name = models.CharField(max_length=255) middle_name = models.CharField(max_length=255) last_name = models.CharField(max_length=255) position = PositionField() client = models.ForeignKey('nc_clients.Client') data_items = GenericRelation('nc_core.DataItem') @cached_property def full_name(self): return " ".join((self.last_name, self.first_name, self.middle_name)) class Meta: verbose_name = _("Employee") verbose_name_plural = _("Employees") def __str__(self): return self.full_name
class ProjectVisual(models.Model): """ Image, video, or animation for a project. """ FILETYPE_IMAGE = 1 FILETYPE_VIDEO = 2 FILETYPE_FLASH = 3 FILETYPE_CHOICES = ( (FILETYPE_IMAGE, 'Image'), (FILETYPE_VIDEO, 'Video'), (FILETYPE_FLASH, 'Flash'), ) file = models.FileField(upload_to=get_upload_path) filetype = models.PositiveSmallIntegerField(choices=FILETYPE_CHOICES, default=FILETYPE_IMAGE) title = models.CharField() caption = models.CharField() project = models.ForeignKey(Project) order = PositionField(collection='project', default=0) public = models.BooleanField(default=True) lead = models.BooleanField(default=False) def get_upload_path(instance, filename): return os.path.join('visuals', 'project', instance.project.title, filename) @property def filename(self): return os.path.basename(self.file.name) def __unicode__(self): if self.title: return u'%s' % self.title else: return u'%s' % self.filename def save(self, *args, **kwargs): if self.lead: related_files = self._default_manager.filter(project=self.project) related_files.update(lead=False) return super(ProjectVisual, self).save(*args, **kwargs)
class Unit(models.Model): """Represents a PDF of a unit, with problems and solutions""" group = models.ForeignKey( UnitGroup, on_delete=models.CASCADE, help_text="The group that this unit belongs to", ) code = models.CharField( max_length=255, help_text="The version code for the handout, like 'ZGX'", ) position = PositionField(help_text="The ordering of the relative handouts to each other.") def __str__(self) -> str: if self.group is not None: return self.group.name + " [" + self.code + "]" return "-" + " [" + self.code + "]" class Meta: unique_together = ('group', 'code') ordering = ('position', ) @property def list_display_position(self): return self.position @property def problems_pdf_filename(self) -> str: return self.code + '-' + self.group.slug + '.pdf' @property def solutions_pdf_filename(self) -> str: return self.code + '-sol-' + self.group.slug + '.pdf' @property def problems_tex_filename(self) -> str: return self.code + '-tex-' + self.group.slug + '.tex' def get_absolute_url(self): return reverse("view-problems", args=(self.pk, ))
class Unit(models.Model): title = models.CharField(_('Title'), max_length=128, blank=True) lesson = models.ForeignKey(Lesson, verbose_name=_('Lesson'), related_name='units') video = models.ForeignKey(Video, verbose_name=_('Video'), null=True, blank=True) activity = models.ForeignKey('activities.Activity', verbose_name=_('Activity'), null=True, blank=True, related_name='units') side_notes = models.TextField(_('Side notes'), blank=True) position = PositionField(collection='lesson', default=0) notes = generic.GenericRelation(Note) class Meta: verbose_name = _('Unit') verbose_name_plural = _('Units') ordering = ['lesson', 'position'] def __unicode__(self): return u'%s - %s - %s - %s' % (self.lesson, self.position, self.video, self.activity) @staticmethod def set_position_for_new_unit(sender, instance, **kwargs): if instance.id: return latest = sender.objects.filter(lesson=instance.lesson) \ .aggregate(models.Max('position')) \ .get('position__max') if latest is not None: instance.position = latest + 1
class ChainedItem(models.Model): """ parameters of a given item in a given chain. Processors and other chains can appear in any chain at any position. This depends on the complexity of the parent chain. The current model holds the position and the optional arguments of all chained items. """ __metaclass__ = TransMeta class Meta: app_label = 'core' verbose_name = _(u'Chained item') verbose_name_plural = _(u'Chained items') translate = ('notes', ) objects = ChainedItemManager() chain = models.ForeignKey(ProcessingChain, related_name='chained_items') item_type = models.ForeignKey( ContentType, null=True, blank=True, limit_choices_to=( models.Q(app_label='core') & ( # HEADS UP: “Processor” (title case, model name) does # not find anything. “processor” (lowercase) works. See: # # ContentType.objects.filter( # app_label='core').values_list( # 'model', flat=True) # # For a complete list of valid values. models.Q(model='processor') | models.Q(model='processingchain') ) ) ) item_id = models.PositiveIntegerField(null=True, blank=True) item = generic.GenericForeignKey('item_type', 'item_id') position = PositionField(collection=('chain', ), default=0, blank=True) is_active = models.BooleanField(default=True) parameters = YAMLField( null=True, blank=True, verbose_name=_(u'Processing parameters'), help_text=_(u'Parameters for this processor, in this chain, at ' u'this position. Can be left empty if the processor ' u'parameters are optional. Can be overridden, by ' u'order of importance, by website, feed or item-level ' u'processing parameters. In YAML format (see ' u'http://en.wikipedia.org/wiki/YAML for details).')) is_valid = models.BooleanField(verbose_name=_(u'Checked and valid'), default=True, blank=True) check_error = models.CharField(max_length=255, null=True, blank=True) notes = models.TextField( null=True, blank=True, verbose_name=_(u'Notes'), help_text=_(u'Things to know about this processor, ' u'in this chain, at this position.')) # ————————————————————————————————————————————————————————— Python & Django def __unicode__(self): """ I'm __unicode__, pep257. """ return ( u'Chain {0} pos. {1}: {2} {3} (#{4})'.format( self.chain.slug, self.position, self.item._meta.verbose_name, self.item.slug, self.id) ) def natural_key(self): """ This is needed for serialization. """ return (self.chain, self.position)
class Course(models.Model): STATES = ( ('new', _('New')), ('draft', _('Draft')), ('listed', _('Listed')), ('published', _('Published')), ) slug = models.SlugField(_('Slug'), max_length=255, unique=True) name = models.CharField(_('Name'), max_length=255, blank=True) intro_video = models.ForeignKey(Video, verbose_name=_('Intro video'), null=True, blank=True) application = models.TextField(_('Application'), blank=True) requirement = models.TextField(_('Requirement'), blank=True) abstract = models.TextField(_('Abstract'), blank=True) structure = models.TextField(_('Structure'), blank=True) workload = models.TextField(_('Workload'), blank=True) pronatec = models.TextField(_('Pronatec'), blank=True) status = models.CharField(_('Status'), choices=STATES, default=STATES[0][0], max_length=64) publication = models.DateField(_('Publication'), default=None, blank=True, null=True) thumbnail = models.ImageField(_('Thumbnail'), upload_to='course_thumbnails', null=True, blank=True) professors = models.ManyToManyField(TimtecUser, related_name='professorcourse_set', through='CourseProfessor') students = models.ManyToManyField(TimtecUser, related_name='studentcourse_set', through='CourseStudent') home_thumbnail = models.ImageField(_('Home thumbnail'), upload_to='home_thumbnails', null=True, blank=True) home_position = PositionField(blank=True, null=True) start_date = models.DateField(_('Start date'), default=None, blank=True, null=True) home_published = models.BooleanField(default=False) class Meta: verbose_name = _('Course') verbose_name_plural = _('Courses') def __unicode__(self): return self.name @property def unit_set(self): return Unit.objects.filter( lesson__in=self.lessons.all()).order_by('lesson') @property def public_lessons(self): return self.lessons.exclude(status='draft') def first_lesson(self): if self.lessons.exists(): return self.lessons.all()[0] def enroll_student(self, student): params = {'user': student, 'course': self} try: return CourseStudent.objects.get(**params) except CourseStudent.DoesNotExist: return CourseStudent.objects.create(**params) def get_thumbnail_url(self): if self.thumbnail: return self.thumbnail.url return '' @property def has_started(self): if self.start_date <= datetime.date.today(): return True else: return False def avg_lessons_users_progress(self): student_enrolled = self.coursestudent_set.all().count() progress_list = [] for lesson in self.lessons.all(): lesson_progress = {} lesson_progress['name'] = lesson.name lesson_progress['slug'] = lesson.slug lesson_progress['position'] = lesson.position units_len = lesson.unit_count() if units_len: units_done_len = StudentProgress.objects.exclude( complete=None).filter(unit__lesson=lesson).count() lesson_progress['progress'] = 100 * units_done_len / ( units_len * student_enrolled) lesson_progress[ 'forum_questions'] = lesson.forum_questions.count() # lesson_progress['progress'] = # lesson_progress['finish'] = self.get_lesson_finish_time(lesson) else: lesson_progress['progress'] = 0 # lesson_progress['finish'] = '' progress_list.append(lesson_progress) return progress_list def forum_answers_by_lesson(self): return self.user.forum_answers.values('question__lesson').annotate( Count('question__lesson'))
class TwitterFeedRule(ModelDiffMixin): """ Twitter feed rule. A twitter feed can have one or more rule. Each rule can apply to one or more twitter accounts. If the account is null, the rule will apply to all accounts. """ class Meta: app_label = 'core' verbose_name = _(u'Twitter feed rule') verbose_name_plural = _(u'Twitter feed rules') ordering = ('group', 'position', ) INPLACEEDIT_PARENTCHAIN = ('twitterfeed', ) twitterfeed = models.ForeignKey(TwitterFeed, related_name='rules') group = models.IntegerField(verbose_name=_(u'Rules group'), null=True, blank=True) group_operation = models.IntegerField( verbose_name=_(u'Rules group operation'), default=TWITTER_GROUP_OPERATION_DEFAULT, blank=True, choices=TWITTER_RULES_OPERATIONS.get_choices(), help_text=_(u'Condition between rules of this group.') ) match_field = models.IntegerField( verbose_name=_(u'Field'), default=TWITTER_MATCH_FIELD_DEFAULT, blank=True, choices=TWITTER_MATCH_FIELDS.get_choices(), help_text=_(u"E-twitter field on which the match type is performed.") ) match_type = models.IntegerField( verbose_name=_(u'Match type'), default=TWITTER_MATCH_TYPE_DEFAULT, blank=True, choices=TWITTER_MATCH_TYPES.get_choices(), help_text=_(u"Operation applied on the field " u"to compare with match value.") ) match_case = models.BooleanField( verbose_name=_(u'Match case'), default=False, blank=True, help_text=_(u"Do we care about uppercase and lowercase characters?") ) match_value = models.CharField( verbose_name=_(u'Match value'), max_length=1024, null=True, blank=True, help_text=_(u"Examples: “Tweet from”, “Google Alert:”. " u"Can be any text.")) # # De-activated, considered too complex to handle. # This information is already in the twitterfeed. # # match_action = models.CharField( # verbose_name=_(u'Action when matched'), # max_length=10, null=True, blank=True, # choices=tuple(TwitterFeed.MATCH_ACTION_CHOICES.items()), # help_text=_(u'Choose nothing to execute ' # u'action defined at the feed level.')) # # finish_action = models.CharField( # verbose_name=_(u'Finish action'), # max_length=10, null=True, blank=True, # choices=tuple(TwitterFeed.FINISH_ACTION_CHOICES.items()), # help_text=_(u'Choose nothing to execute ' # u'action defined at the feed level.')) # other_field = models.CharField( verbose_name=_(u'Other field'), max_length=255, null=True, blank=True, help_text=_(u"Specify here if you chose “Other field” " u"in previous field.") ) # Used to have many times the same rule in different feeds clone_of = models.ForeignKey('TwitterFeedRule', null=True, blank=True) position = PositionField(collection=('twitterfeed', 'group', ), default=0, blank=True) is_valid = models.BooleanField(verbose_name=_(u'Checked and valid'), default=True, blank=True) check_error = models.CharField(max_length=255, null=True, blank=True) # —————————————————————————————————————————————————————————————— Properties @property def operation(self): """ Return a Python function doing the operation of the rule. Cache it in an attribute to avoid redoing all the work each time this property is called, because in many cases the rule will be called more than once (on multiple messages of multiple twitterboxes). """ try: return self._operation_ except AttributeError: def mymethodcaller(name): def caller(a, b): return getattr(a, name)(b) return caller def ncontains(a, b): return not operator.contains(a, b) def nstarts(a, b): return not a.startswith(b) def nends(a, b): return not a.endswith(b) if self.match_type in (TWITTER_MATCH_TYPES.RE_MATCH, TWITTER_MATCH_TYPES.NRE_MATCH): # The .lower() should work also with the RE. It # should even be faster than a standard /I match(). compiled_re = re.compile(self.match_value if self.match_case else self.match_value.lower()) def re_match(a, b): """ :param:`b` is ignored, it's here for call compat only. """ return bool(compiled_re.match(a)) def nre_match(a, b): return not re_match(a, b) OPERATIONS = { TWITTER_MATCH_TYPES.CONTAINS: operator.contains, TWITTER_MATCH_TYPES.NCONTAINS: ncontains, TWITTER_MATCH_TYPES.STARTS: mymethodcaller('startswith'), TWITTER_MATCH_TYPES.NSTARTS: nstarts, TWITTER_MATCH_TYPES.ENDS: mymethodcaller('endswith'), TWITTER_MATCH_TYPES.NENDS: nends, TWITTER_MATCH_TYPES.EQUALS: operator.eq, TWITTER_MATCH_TYPES.NEQUALS: operator.ne, TWITTER_MATCH_TYPES.RE_MATCH: re.match, TWITTER_MATCH_TYPES.NRE_MATCH: nre_match, } self._operation_ = OPERATIONS[self.match_type] return self._operation_ # —————————————————————————————————————————————————————————————————— Django def __unicode__(self): """ OMG, that's __unicode__, pep257. """ return _(u'Rule #{0}: {2} for TwitterFeed {1}').format( self.id, self.twitterfeed, _(u'{0} {1} “{2}”').format( self.other_field if self.match_field == u'other' else TWITTER_MATCH_FIELDS[self.match_field], TWITTER_MATCH_TYPES[self.match_type], self.match_value, # TwitterFeed.MATCH_ACTION_CHOICES.get(self.match_action, # _(u'feed default')), # TwitterFeed.FINISH_ACTION_CHOICES.get(self.finish_action, # _(u'feed default')), )) def repr_for_json(self): """ Return our attributes in a JSON-compatible form. """ return { 'id': self.id, 'group': self.group, 'group_operation': self.group_operation, 'position': self.position, 'match_field': self.match_field, 'other_field': self.other_field, 'match_type': self.match_type, 'match_value': self.match_value, } def save(self, *args, **kwargs): """ Check the rule is valid before saving. """ changed_fields = self.changed_fields if 'match_field' in changed_fields \ or 'other_field' in changed_fields \ or 'match_type' in changed_fields \ or'match_value' in changed_fields: self.check_is_valid(commit=False) super(TwitterFeedRule, self).save(*args, **kwargs) # ——————————————————————————————————————————————————————————————— Internals def check_is_valid(self, commit=True): """ Check if the rule is appliable or not, and mark it as such. """ return True raise NotImplementedError('Impletment TwitterRule.check_is_valid') is_valid = True if self.match_field == TWITTER_MATCH_FIELDS.OTHER: other = self.other_field if other.strip().endswith(':'): self.other_field = other = other.strip()[:-1] if other.lower() not in OTHER_VALID_HEADERS_lower: is_valid = False self.check_error = _(u'Unrecognized field name “{0}”. Please ' u'look at http://bit.ly/smtp-fields ' u'to find a list of valid fields. ' u'Perhaps just a typo?').format(other) if self.match_type in (TWITTER_MATCH_TYPES.RE_MATCH, TWITTER_MATCH_TYPES.NRE_MATCH): try: re.compile(self.match_value) except Exception as e: is_valid = False self.check_error = _(u'Invalid regular expression “{0}”: ' u'{1}').format(self.match_value, unicode(e)) if is_valid != self.is_valid: self.is_valid = is_valid if is_valid and self.check_error: self.check_error = u'' if commit: self.save() def match_message(self, message): """ True if :param:`message` matches the current rule or its group. """ if self.group: return self.match_message_in_group(message) else: return self.match_message_individual(message) def match_message_in_group(self, message): """ Return True if our rule group says so. """ operation_any = self.group_operation == RULES_OPERATIONS.ANY operation_all = not operation_any rules_group = self.twitterfeed.rules.filter(group=self.group) for rule in rules_group: if rule.match_message_individual(message): if operation_any: # First match makes the group be true. return True else: if operation_all: # First non-match kills the group. return False # OK, this is kind of a nice shortcut. return operation_all def match_message_individual(self, message): """ Test message against the current rule, member of a group or not. """ def match_field(field, value): if not self.match_case: field = field.lower() return self.operation(field, value) HEADERS = BASE_HEADERS.copy() HEADERS[u'other'] = self.other_field # # TODO: implement body searching. # if self.match_case: value = self.match_value else: # HEADS UP: we don't care for the RE; the # second parameter in that case is ignored. value = self.match_value.lower() for field_name in HEADERS[self.match_field]: field = message.get(field_name, u'') if isinstance(field, list) or isinstance(field, tuple): if len(field) > 2: for field_part in field: if isinstance(field, list) \ or isinstance(field, tuple): field = u'{0} {1}'.format(*field_part) if match_field(field, value): return True else: if match_field(field_part, value): return True else: if field[1].startswith(u'<'): # Here we've got [u'Olivier Cortès', '<*****@*****.**>'] # it's the same person; one test. field = u'{0} {1}'.format(*field) if match_field(field, value): return True else: # There we've got [u'Toto <*****@*****.**>', u'Tutu <*****@*****.**>'] # They are 2 different persons and 2 tests. for field_part in field: if match_field(field_part, value): return True else: if match_field(field, value): return True return False
class Column(TimeStampedModel): CACHE_PREFIX = "Column#" TYPE_CHOICES = Choices( ('regular', 'REGULAR', _('regular')), ('first', 'FIRST', _('start workflow')), ('last', 'LAST', _('end workflow')), ) title = models.CharField(verbose_name=_('title'), max_length=512, blank=True, default='') board = models.ForeignKey('nc_workflow.Board', verbose_name=_('board'), related_name='columns', on_delete=models.CASCADE) users = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_("Users")) type = models.CharField(verbose_name=_('type'), max_length=32, choices=TYPE_CHOICES, default=TYPE_CHOICES.REGULAR) position = PositionField(verbose_name=_('position'), collection='board') class Meta(object): verbose_name = _('column') verbose_name_plural = _('columns') ordering = ('position', ) def __str__(self): return self.title def save(self, *args, **kwargs): """ Переопределяем save() для того что бы начальная и конечная колонки были уникальными """ columns = self.board.columns.exclude(id=self.id) if self.type in (Column.TYPE_CHOICES.FIRST, Column.TYPE_CHOICES.LAST): columns.filter(type=self.type).update( type=self.TYPE_CHOICES.REGULAR) # raise IntegrityError(_("Board already contains first/last column")) super(Column, self).save(*args, **kwargs) @cached_property def has_subprocess_from(self): "There is a cross-boards transition from this column" from .subprocess import Subprocess try: return bool(self.subprocess_from) except Subprocess.DoesNotExist: return False @cached_property def has_subprocess_to(self): "There're cross-boards transitions to this column" return self.subprocess_to.exists() @memcached_property( key=lambda self: self.CACHE_PREFIX + str(self.id), timeout=30 # seconds ) def is_subprocess(self): """ @manually_invalidated Alias to has_subprocess_from """ return self.has_subprocess_from @cached_property def is_start_workflow(self): return self.type == self.TYPE_CHOICES.FIRST @cached_property def is_end_workflow(self): return self.type == self.TYPE_CHOICES.LAST def delete(self, using=None, keep_parents=False): from . import Card self.cards.update(status=Card.STATUS_CHOICES.BASKET, basket_date=timezone.now()) return super(Column, self).delete(using=using, keep_parents=keep_parents)
class Item(models.Model): menu = models.ForeignKey(Menu) position = PositionField(collection='menu')
class Item(models.Model): menu = models.ForeignKey(Menu, on_delete=models.CASCADE) position = PositionField(collection='menu')