Пример #1
0
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
Пример #2
0
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')
Пример #3
0
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
Пример #4
0
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()
Пример #5
0
class SizeGroup(models.Model):
    name = models.CharField(max_length=30, unique=True)
    position = PositionField()
    
    def __str__(self):
        return self.name

    class Meta:
        ordering = ['position']
Пример #6
0
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)
Пример #7
0
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)
Пример #8
0
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']
Пример #9
0
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']
Пример #10
0
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()
Пример #11
0
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)
Пример #12
0
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
Пример #13
0
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
Пример #14
0
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
Пример #15
0
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)
Пример #16
0
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
Пример #17
0
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
Пример #18
0
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"
Пример #19
0
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']
Пример #20
0
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()
Пример #21
0
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
Пример #22
0
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)
Пример #23
0
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, ))
Пример #24
0
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
Пример #25
0
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)
Пример #26
0
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'))
Пример #27
0
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)
Пример #29
0
class Item(models.Model):
    menu = models.ForeignKey(Menu)
    position = PositionField(collection='menu')
Пример #30
0
class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    position = PositionField(collection='menu')