コード例 #1
0
class CookBookRecipe(models.Model):

    cookbook = CachedForeignKey('CookBook')
    recipe = CachedForeignKey('Recipe')
    note = models.CharField(_("Note"), max_length=255, blank=True)
    added = models.DateField(_("Added"))

    class Meta:
        unique_together = (('cookbook', 'recipe'), )
        verbose_name = _("Cookbook's recipes")
        verbose_name = _("Cookbooks' recipes")

    def save(self, *args, **kwargs):
        if not self.pk:
            self.added = date.today()

        ret_value = super(CookBookRecipe, self).save(*args, **kwargs)

        self.cookbook.get_recipes_count(recache=True)
        self.cookbook.__class__.objects.get_user_cookbook_items_for_recipe(
            self.cookbook.owner, self.recipe, recache=True)
        return ret_value

    def delete(self, *args, **kwargs):
        cookbook = self.cookbook
        recipe = self.recipe
        super(CookBookRecipe, self).delete(*args, **kwargs)
        cookbook.get_recipes_count(recache=True)
        cookbook.__class__.objects.get_user_cookbook_items_for_recipe(
            cookbook.owner, recipe, recache=True)
コード例 #2
0
class SubstituteIngredient(models.Model):

    ingredient = CachedForeignKey(Ingredient, verbose_name=_('Ingredient'))
    substitute = CachedForeignKey(Ingredient,
                                  verbose_name=_('Substitute ingredient'),
                                  related_name='substitute_ingredients')

    objects = managers.SubstituteIngredientManager()

    def __unicode__(self):
        return force_text(
            _("%(sub)s is substitute for %(ing)s") % {
                'sub': self.substitute,
                'ing': self.ingredient,
            })

    class Meta:
        verbose_name = _('Substitute ingredient')
        verbose_name_plural = _('Substitute ingredients')

    def save(self, *args, **kwargs):
        try:
            self.clean()
        except ValidationError, e:
            raise IntegrityError(e.messages)

        super(SubstituteIngredient, self).save(*args, **kwargs)
コード例 #3
0
class ShoppingList(models.Model):

    owner = CachedForeignKey(User)
    title = models.CharField(_("Title"), max_length=155)
    note = models.TextField(_("Note"), blank=True)

    def __unicode__(self):
        return u"%s: %s" % (_("Shopping list"), self.title)

    class Meta:
        verbose_name = _("Shopping list")
        verbose_name_plural = _("Shopping lists")
コード例 #4
0
class RecipePhoto(models.Model):

    objects = managers.RecipePhotoManager()

    recipe = CachedForeignKey(Recipe, verbose_name=_('Recipe'))
    photo = CachedForeignKey(Photo, verbose_name=_('Photo'))
    is_visible = models.BooleanField(_('Visible'), default=True)
    is_checked = models.BooleanField(_('Checked'), default=False)
    order = models.PositiveSmallIntegerField(_('Order'),
                                             db_index=True,
                                             blank=True)

    def __unicode__(self):
        return u"%d. %s" % (self.order, self.photo)

    class Meta:
        unique_together = (
            ('recipe', 'photo'),
            ('recipe', 'order'),
        )
        verbose_name = _('Recipe photo')
        verbose_name_plural = _('Recipe photos')

    def save(self, *args, **kwargs):
        if not self.order:
            order = RecipePhoto.objects.filter(recipe=self.recipe).values_list(
                'order', flat=True).order_by("-order")
            self.order = order[0] + 1 if order else 1
        super(RecipePhoto, self).save(*args, **kwargs)

    @classmethod
    def _bump_photos(cls, *args, **kwargs):
        recipe_id = kwargs.get('instance').recipe_id
        try:
            recipe = Recipe.objects.get(pk=recipe_id)
        except Recipe.DoesNotExist:
            pass
        else:
            recipe.get_photos(recache=True)
コード例 #5
0
class ShoppingListItem(models.Model):

    shopping_list = CachedForeignKey(ShoppingList)
    ingredient = CachedForeignKey(Ingredient, verbose_name=_('Ingredient'))
    amount = models.DecimalField(_('Amount'),
                                 max_digits=5,
                                 decimal_places=2,
                                 null=True,
                                 blank=True)
    unit = models.PositiveSmallIntegerField(_('Unit'),
                                            choices=conf.UNIT_CHOICES,
                                            null=True,
                                            blank=True)
    note = models.CharField(_('Note'), max_length=255, blank=True)

    def __unicode__(self):
        return u"%s %s %s" % (self.ingredient, _("in shopping list"),
                              self.shopping_list.title)

    class Meta:
        unique_together = (('shopping_list', 'ingredient'), )
        verbose_name = _("Shopping list item")
        verbose_name_plural = _("Shopping list items")
コード例 #6
0
class WeekMenu(models.Model):

    day = models.IntegerField(_("Day of the week"), choices=conf.WEEK_DAYS)
    soup = CachedForeignKey(Recipe,
                            blank=True,
                            null=True,
                            related_name="menu_soup",
                            verbose_name=_('Soup'))
    meal = CachedForeignKey(Recipe,
                            blank=True,
                            null=True,
                            related_name="menu_meal",
                            verbose_name=_('Meal'))
    dessert = CachedForeignKey(Recipe,
                               blank=True,
                               null=True,
                               related_name="menu_dessert",
                               verbose_name=_('Dessert'))
    even_week = models.BooleanField(
        _("Menu for even week"),
        default=False,
        help_text=string_concat(
            _("Check if this day menu is for even week. Current week is "),
            _("odd") if date.isocalendar(date.today())[1] % 2 else _("even"),
            "."))

    objects = managers.WeekMenuManager()

    class Meta:
        unique_together = (('day', 'even_week'), )
        verbose_name = _("Menu of the day")
        verbose_name_plural = _("Menus of the day")

    def __unicode__(self):
        return u"%s week, day %s" % (_("Even") if self.even_week else _("Odd"),
                                     self.get_day_display())
コード例 #7
0
class CookBook(models.Model):

    owner = CachedForeignKey(User)
    title = models.CharField(_("Title"), max_length=128)
    slug = models.SlugField(_("Slug"), max_length=128)
    is_public = models.BooleanField(_("Public"), default=True)
    recipes = models.ManyToManyField(Recipe,
                                     verbose_name=_('Recipes'),
                                     through=CookBookRecipe)
    is_default = models.BooleanField(_("Default cookbook"), default=False)

    objects = managers.CookBookManager()

    def __unicode__(self):
        return u"%s's cookbok: %s" % (self.owner, self.title)

    class Meta:
        unique_together = (('owner', 'slug'), )
        verbose_name = _('Cookbook')
        verbose_name_plural = _('Cookbooks')

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        return super(CookBook, self).save(*args, **kwargs)

    def get_recipes_count(self, recache=False):
        cache_key = "%s_get_cookbook_recipes" % self.pk
        recipes_count = cache.get(cache_key)
        if recipes_count is None or recache:
            recipes_count = CookBookRecipe.objects.filter(
                cookbook=self).count()
            cache.set(cache_key, recipes_count)
        return recipes_count

    def get_top_photo(self):
        recipes = self.recipes.all().order_by('id')[:1]
        if not recipes:
            return ""
        return recipes[0].top_photo

    @cached_property
    def top_photo(self):
        return self.get_top_photo()

    def get_absolute_url(self):
        owner = self.owner
        return reverse('yummy:cookbook_detail',
                       args=(slugify(owner.username), owner.pk, self.slug))
コード例 #8
0
class Ingredient(models.Model):

    group = CachedForeignKey(IngredientGroup,
                             verbose_name=_('Group'),
                             null=True,
                             blank=True)
    name = models.CharField(_('Name'), max_length=128)
    slug = models.SlugField(_('Slug'), max_length=64, unique=True)
    genitive = models.CharField(_('Genitive'), max_length=128, blank=True)
    default_unit = models.PositiveSmallIntegerField(
        choices=conf.UNIT_CHOICES,
        verbose_name=_('Default unit'),
        null=True,
        blank=True)

    ndb_no = models.IntegerField(_('NDB id number'), blank=True, null=True)
    is_approved = models.BooleanField(_('Approved'),
                                      default=True,
                                      db_index=True)

    objects = managers.IngredientManager()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _('Ingredient')
        verbose_name_plural = _('Ingredients')

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super(Ingredient, self).save(*args, **kwargs)

        Ingredient.objects.get_names_list(recache=True)

    def get_absolute_url(self):
        return reverse('yummy:ingredient_detail', args=(self.slug, ))

    @cached_property
    def substitutes(self):
        return SubstituteIngredient.objects.get_for_ingredient_cached(self)
コード例 #9
0
class IngredientInRecipeGroup(models.Model):

    recipe = CachedForeignKey(Recipe, verbose_name=_('Recipe'))
    title = models.CharField(_('Title'), max_length=128)
    description = models.TextField(_('Short description'), blank=True)
    order = models.PositiveSmallIntegerField(_('Order'),
                                             db_index=True,
                                             blank=True)

    def __unicode__(self):
        return u"%s %s" % (self.recipe, self.title)

    class Meta:
        verbose_name = _('Ingredients in recipe group')
        verbose_name_plural = _('Ingredients in recipe groups')

    def save(self, *args, **kwargs):
        if not self.order:
            self.order = IngredientInRecipeGroup.objects.filter(
                recipe=self.recipe).count() + 1
        super(IngredientInRecipeGroup, self).save(*args, **kwargs)
コード例 #10
0
class FlatComment(models.Model):
    site = SiteForeignKey(default=Site.objects.get_current)

    content_type = ContentTypeForeignKey()
    object_id = models.CharField(max_length=255)
    content_object = CachedGenericForeignKey('content_type', 'object_id')

    comment = models.TextField()

    submit_date = models.DateTimeField(default=None)
    user = CachedForeignKey(User)
    is_public = models.BooleanField(default=True)

    app_data = AppDataField()

    def _comment_list(self, reversed=False):
        if not hasattr(self, '__comment_list'):
            self.__comment_list = CommentList(self.content_type,
                                              self.object_id, reversed)
        return self.__comment_list

    def post(self, request=None):
        return self._comment_list().post_comment(self, request)

    def moderate(self, user=None, commit=True):
        return self._comment_list().moderate_comment(self, user, commit)

    def get_absolute_url(self, reversed=False):
        return '%s?p=%d' % (resolver.reverse(
            self.content_object,
            'comments-list'), self._comment_list(reversed).page_index(self.pk))

    def delete(self):
        self.moderate()
        super(FlatComment, self).delete()

    def save(self, **kwargs):
        if self.submit_date is None:
            self.submit_date = timezone.now()
        super(FlatComment, self).save(**kwargs)
コード例 #11
0
class RecipeRecommendation(models.Model):

    objects = managers.RecipeRecommendationManager()

    day_from = models.DateField(
        _("Show from day"),
        help_text=_("Recipe will show itself starting this day"))
    day_to = models.DateField(
        _("Show until day (inclusive)"),
        blank=True,
        null=True,
        help_text=_("Recipe shown until this day. This field is not required. "
                    "The longer is recipe shown, the lower priority it has."))
    recipe = CachedForeignKey(Recipe)

    def __unicode__(self):
        return u"'%s', %s - %s" % (self.recipe, self.day_from,
                                   (self.day_to or _('until forever')))

    class Meta:
        verbose_name = _("Recipe recommendation")
        verbose_name_plural = _("Recipe recommendations")

    def clean(self):
        if not self.recipe.is_approved:
            raise ValidationError(
                _("You can save recommendation only with approved recipe"))

        if self.day_to and self.day_to < self.day_from:
            raise ValidationError(_("Invalid chronology of border dates"))

    def save(self, *args, **kwargs):
        try:
            self.clean()
        except ValidationError, e:
            raise IntegrityError(e.messages)

        super(RecipeRecommendation, self).save(*args, **kwargs)
コード例 #12
0
class IngredientInRecipe(models.Model):

    recipe = CachedForeignKey(Recipe, verbose_name=_('Recipe'))
    group = CachedForeignKey(IngredientInRecipeGroup,
                             verbose_name=_('Group'),
                             null=True,
                             blank=True)
    ingredient = CachedForeignKey(Ingredient, verbose_name=_('Ingredient'))
    amount = models.DecimalField(_('Amount'),
                                 max_digits=5,
                                 decimal_places=2,
                                 null=True,
                                 blank=True)
    unit = models.PositiveSmallIntegerField(_('Unit'),
                                            choices=conf.UNIT_CHOICES,
                                            null=True,
                                            blank=True)
    order = models.PositiveSmallIntegerField(_('Order'),
                                             db_index=True,
                                             blank=True)
    note = models.CharField(_('Note'), max_length=255, blank=True)

    def __unicode__(self):
        return u"%s - %s" % (self.ingredient, self.recipe)

    class Meta:
        verbose_name = _('Ingredient in recipe')
        verbose_name_plural = _('Ingredients in recipe')

    def save(self, *args, **kwargs):
        if not self.order:
            self.order = IngredientInRecipe.objects.filter(
                recipe=self.recipe).count() + 1
        super(IngredientInRecipe, self).save(*args, **kwargs)

    @property
    def inflect_unit(self):
        def _get_magic_unit():
            if conf.ALLOW_MAGIC_UNITS_TRANSFORM:
                res = f(5)
                res = ''.join([res[0:-1], slugify(res[-1])])
            else:
                res = f(1)
            return res

        if self.unit is None or self.amount is None:
            return ""
        f, amount_for_decimal = conf.DICT_UNITS[self.unit]
        i = int(self.amount)

        # if decimal amount is equal to int(amount) return
        if self.amount == i:
            return f(i)

        # else construct magic unit
        num_parts = str(self.amount).split(".")
        if len(num_parts) == 1:
            res = f(self.amount)
        else:
            res = amount_for_decimal == 'm' and _get_magic_unit() or f(
                amount_for_decimal)
        return res
コード例 #13
0
class Recipe(models.Model):

    objects = managers.RecipeManager()

    title = models.CharField(_('Title'), max_length=128)
    slug = models.SlugField(_('Slug'), max_length=64, unique=True)
    category = CachedForeignKey(Category, verbose_name=_("Category"))

    description = models.TextField(_('Short description'), blank=True)
    preparation = models.TextField(_('Preparation'))
    hint = models.TextField(_('Hint'), blank=True)

    cooking_type = CachedForeignKey(CookingType,
                                    verbose_name=_('Cooking type'),
                                    blank=True,
                                    null=True)
    cuisines = models.ManyToManyField(Cuisine,
                                      verbose_name=_('Cuisines'),
                                      blank=True)
    servings = models.PositiveSmallIntegerField(
        _('No. of servings'),
        choices=getattr(settings, 'YUMMY_SERVINGS_CHOICES', None),
        blank=True,
        null=True)

    price = models.SmallIntegerField(_('Price'),
                                     choices=conf.PRICING_CHOICES,
                                     default=3,
                                     db_index=True,
                                     null=True,
                                     blank=True)
    difficulty = models.PositiveSmallIntegerField(
        _('Preparation difficulty'),
        choices=conf.DIFFICULTY_CHOICES,
        default=3,
        db_index=True,
        null=True,
        blank=True)
    preparation_time = models.PositiveSmallIntegerField(
        _('Preparation time (min)'), blank=True, null=True)
    caloric_value = models.PositiveIntegerField(_('Caloric value'),
                                                blank=True,
                                                null=True)

    owner = CachedForeignKey(User, verbose_name=_('User'))
    is_approved = models.BooleanField(_('Approved'),
                                      default=False,
                                      db_index=True)
    is_public = models.BooleanField(_('Public'), default=True)
    is_checked = models.BooleanField(_("Is checked"), default=False)
    created = models.DateTimeField(editable=False, db_index=True)
    updated = models.DateTimeField(editable=False)

    def __unicode__(self):
        return self.title

    def save(self, **kwargs):
        self.updated = now()
        if not self.id:
            self.created = self.updated

        if not self.slug:
            self.slug = slugify(self.title)
        super(Recipe, self).save(**kwargs)

        self.groupped_ingredients(recache=True)
        self.category.get_recipes_count(recache=True)

    class Meta:
        verbose_name = _('Recipe')
        verbose_name_plural = _('Recipes')
        permissions = (("approve_recipe", "Can approve recipe"), )

    def get_photos(self, recache=False):
        cache_key = '%s_recipe_photos' % self.pk
        cached_photos = cache.get(cache_key)
        if cached_photos is None or recache:
            cached_photos = []
            qs = self.recipephoto_set.visible().select_related(
                'photo').order_by('order')
            for one in qs:
                if one.photo.owner_id == self.owner.pk:
                    cached_photos.insert(0, one.photo)
                else:
                    cached_photos.append(one.photo)
            cache.set(cache_key, cached_photos)

        return cached_photos

    def get_top_photo(self):
        """
        Get to photo for recipe. Prefer photo from recipe's owner, if available.
        If recipe doesn't have any photo, try to get photo for recipe's category

        :return: photo for recipe
        :rtype: Photo
        """
        photos = self.get_photos()
        if photos:
            return photos[0]
        else:
            return self.category.photo_hierarchic

    @cached_property
    def top_photo(self):
        return self.get_top_photo()

    @recached_method_to_mem
    def groupped_ingredients(self, recache=False):
        """
        order items by group's priority
        if items are not in any group, set them into anonymous group \
            which has highest priority (lowest number)
        to keep priorities while listing, conversion to list is needed

        :param recache: force recache
        :type recache: bool
        :return: list of groups w/ items: (group, {prioriy:1, items:[]}
        :rtype: list
        """
        cache_key = '%s_groupped_ingredients' % self.pk
        groups = cache.get(cache_key)
        if groups is None or recache:
            qs = IngredientInRecipe.objects.filter(
                recipe=self).select_related('ingredient').order_by(
                    'group__order', 'order')

            tmp_groups = {}
            for one in qs:
                group_index = one.group.title if one.group else '__nogroup__'
                group_priority = one.group.order if one.group else 0
                tmp_groups.setdefault(
                    group_index,
                    dict(items=[],
                         priority=group_priority))['items'].append(one)

            groups = sorted(tmp_groups.items(),
                            key=lambda x: x[1].get('priority'))
            cache.set(cache_key, groups)
        return groups

    def get_absolute_url(self):
        return reverse('yummy:recipe_detail',
                       args=(
                           self.category.path,
                           self.slug,
                           self.pk,
                       ))
コード例 #14
0
class Category(models.Model):

    objects = managers.CategoryManager()

    parent = CachedForeignKey('self', null=True, blank=True)
    title = models.CharField(_('Title'), max_length=128)
    slug = models.SlugField(_('Slug'), max_length=64)
    # fake recipe photo
    photo = CachedForeignKey(Photo,
                             verbose_name=_('Photo'),
                             null=True,
                             blank=True)
    path = models.CharField(max_length=255, editable=False, unique=True)
    description = models.TextField(_('Description'), blank=True)

    class Meta:
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')
        ordering = ('path', )

    def __unicode__(self):
        return self.title

    def get_root_ancestor(self):
        if self.parent is not None:
            return self.parent.get_root_ancestor()
        return self

    @property
    def chained_title(self):
        if self.parent:
            return "%s / %s" % (self.parent.chained_title, self.title)
        return self.title

    def get_absolute_url(self):
        return reverse('yummy:category_detail', args=(self.path, ))

    @property
    def is_root_category(self):
        return self.parent is None

    def is_ancestor_of(self, category=None):
        if category is None or category.is_root_category:
            return False
        if category.parent == self:
            return True
        return self.is_ancestor_of(category.parent)

    def get_children(self, recache=False):
        cache_key = "%s_direct_descendants" % self.pk
        cached_cats = cache.get(cache_key)
        if cached_cats is None or recache:
            cached_cats = list(self.__class__.objects.filter(parent=self))
            cache.set(cache_key, cached_cats)
        return cached_cats

    def get_descendants(self, recache=False):
        cats = []
        for child_category in self.get_children(recache):
            cats += [child_category]
            cats += child_category.get_descendants()
        return cats

    @property
    def level(self):
        return len(self.path.split('/'))

    def path_is_unique(self):
        if self.parent:
            path = '%s/%s' % (self.parent.path, self.slug)
        else:
            path = self.slug

        qs = self.__class__.objects.filter(path=path)
        if self.pk:
            qs = qs.exclude(pk=self.pk)
        return not bool(qs.count())

    def clean(self):
        if self == self.parent:
            raise ValidationError(
                _('Parent category must be different than child.'))

        if self.is_ancestor_of(self.parent):
            raise ValidationError(
                _('A parent can\'t be a descendant of this category.'))

        if not self.path_is_unique():
            raise ValidationError(
                _('Path is not unique, change category title or slug.'))

    def save(self, **kwargs):
        """Override save() to construct path based on the category's parent."""
        old_path = self.path
        if not self.slug:
            self.slug = slugify(self.title)

        if self.parent:
            if self == self.parent or self.is_ancestor_of(self.parent):
                raise IntegrityError(
                    'Bad category structure. Check category parent.')
            self.path = '%s/%s' % (self.parent.path, self.slug)
        else:
            self.path = self.slug

        super(Category, self).save(**kwargs)

        if old_path != self.path:
            if self.parent:
                self.parent.get_descendants(recache=True)

            # update descendants' path
            for cat in self.get_descendants():
                cat.save(force_update=True)

    @property
    def photo_hierarchic(self):
        if self.photo:
            return self.photo
        if self.parent:
            return self.parent.photo_hierarchic
        return ""

    def get_recipes_count(self, recache=False):
        cache_key = '%s_subcats_recipes_count' % self.pk
        recipes_count = cache.get(cache_key)
        if recipes_count is None or recache:
            recipes_count = Recipe.objects.filter(is_public=True,
                                                  is_approved=True,
                                                  category=self).count()
            children = Category.objects.filter(parent=self)
            for one in children:
                recipes_count += one.get_recipes_count()
            cache.set(cache_key, recipes_count)

        return recipes_count
コード例 #15
0
class FlatComment(models.Model):
    site = SiteForeignKey(default=Site.objects.get_current)

    content_type = ContentTypeForeignKey()
    object_id = models.CharField(max_length=255)
    content_object = CachedGenericForeignKey('content_type', 'object_id')

    comment = models.TextField()

    submit_date = models.DateTimeField(default=None)
    user = CachedForeignKey(User)
    is_public = models.BooleanField(default=True)

    app_data = AppDataField()

    def _comment_list(self, reversed=False):
        if not hasattr(self, '__comment_list'):
            self.__comment_list = CommentList(self.content_type, self.object_id, reversed)
        return self.__comment_list

    def post(self, request=None):
        return self._comment_list().post_comment(self, request)

    def moderate(self, user=None, commit=True):
        return self._comment_list().moderate_comment(self, user, commit)

    def get_absolute_url(self, reversed=False):
        return '%s?p=%d' % (
                resolver.reverse(self.content_object, 'comments-list'),
                self._comment_list(reversed).page_index(self.pk)
            )

    def delete(self):
        self.moderate()
        super(FlatComment, self).delete()

    def save(self, **kwargs):
        if self.submit_date is None:
            self.submit_date = timezone.now()
        super(FlatComment, self).save(**kwargs)

    def has_edit_timer(self):
        '''has_edit_timer() -> bool
        '''
        return EDIT_TIMER_ENABLED

    def is_edit_timer_expired(self):
        '''is_edit_timer_expired() -> bool
        Check whether the comment is still within the allowed edit time
        from the creation time.
        '''
        age = datetime.now(self.submit_date.tzinfo) - self.submit_date
        if age >= timedelta(minutes=EDIT_TIMER_MINUTES):
            return True
        return False
        
    def get_remaining_edit_time(self):
        '''get_remaining_edit_time() -> str
        Returns the remaining edit time from comment creation. The returned
        string is formatted as HH:MM:SS, e.g. 0:01:23
        '''
        age = datetime.now(self.submit_date.tzinfo) - self.submit_date
        edit_time = timedelta(minutes=EDIT_TIMER_MINUTES)
        if age >= edit_time:
            return '0:00:00'
        seconds = edit_time.total_seconds() - age.total_seconds()
        remaining = timedelta(seconds=seconds)
        text = str(remaining)
        text = text.split('.')[0]
        return text