Exemple #1
0
class ExerciseBase(AbstractSubmissionModel, AbstractLicenseModel,
                   models.Model):
    """
    Model for an exercise base
    """

    objects = SubmissionManager()
    """Custom manager"""

    uuid = models.UUIDField(default=uuid.uuid4,
                            editable=False,
                            verbose_name='UUID')
    """Globally unique ID, to identify the base across installations"""

    category = models.ForeignKey(ExerciseCategory,
                                 verbose_name=_('Category'),
                                 on_delete=models.CASCADE)

    muscles = models.ManyToManyField(Muscle,
                                     blank=True,
                                     verbose_name=_('Primary muscles'))
    """Main muscles trained by the exercise"""

    muscles_secondary = models.ManyToManyField(
        Muscle,
        verbose_name=_('Secondary muscles'),
        related_name='secondary_muscles_base',
        blank=True)
    """Secondary muscles trained by the exercise"""

    equipment = models.ManyToManyField(Equipment,
                                       verbose_name=_('Equipment'),
                                       blank=True)
    """Equipment needed by this exercise"""

    variations = models.ForeignKey(Variation,
                                   verbose_name=_('Variations'),
                                   on_delete=models.CASCADE,
                                   null=True,
                                   blank=True)
    """Variations of this exercise"""

    #
    # Own methods
    #

    @property
    def get_languages(self):
        """
        Returns the languages from the exercises that use this base
        """
        return [exercise.language for exercise in self.exercises.all()]
Exemple #2
0
class Ingredient(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
    """
    An ingredient, with some approximate nutrition values
    """
    objects = SubmissionManager()
    """Custom manager"""

    ENERGY_APPROXIMATION = 15
    """
    How much the calculated energy from protein, etc. can deviate from the
    energy amount given (in percent).
    """

    # Metaclass to set some other properties
    class Meta:
        ordering = [
            "name",
        ]

    # Meta data
    language = models.ForeignKey(
        Language,
        verbose_name=_('Language'),
        editable=False,
        on_delete=models.CASCADE,
    )

    creation_date = models.DateField(_('Date'), auto_now_add=True)
    update_date = models.DateField(
        _('Date'),
        auto_now=True,
        blank=True,
        editable=False,
    )

    # Product infos
    name = models.CharField(
        max_length=200,
        verbose_name=_('Name'),
        validators=[MinLengthValidator(3)],
    )

    energy = models.IntegerField(verbose_name=_('Energy'),
                                 help_text=_('In kcal per 100g'))

    protein = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        verbose_name=_('Protein'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    carbohydrates = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        verbose_name=_('Carbohydrates'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    carbohydrates_sugar = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        blank=True,
        null=True,
        verbose_name=_('Sugar content in carbohydrates'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    fat = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        verbose_name=_('Fat'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    fat_saturated = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        blank=True,
        null=True,
        verbose_name=_('Saturated fat content in fats'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    fibres = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        blank=True,
        null=True,
        verbose_name=_('Fibres'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    sodium = models.DecimalField(
        decimal_places=3,
        max_digits=6,
        blank=True,
        null=True,
        verbose_name=_('Sodium'),
        help_text=_('In g per 100g of product'),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
    )

    code = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        db_index=True,
    )
    """Internal ID of the source database, e.g. a barcode or similar"""

    source_name = models.CharField(
        max_length=200,
        null=True,
        blank=True,
    )
    """Name of the source, such as Open Food Facts"""

    source_url = models.URLField(
        verbose_name=_('Link'),
        help_text=_('Link to product'),
        blank=True,
        null=True,
    )
    """URL of the product at the source"""

    last_imported = models.DateTimeField(
        _('Date'),
        auto_now_add=True,
        null=True,
        blank=True,
    )

    common_name = models.CharField(
        max_length=200,
        null=True,
        blank=True,
    )

    category = models.ForeignKey(
        IngredientCategory,
        verbose_name=_('Category'),
        on_delete=models.CASCADE,
        null=True,
        blank=True,
    )

    brand = models.CharField(
        max_length=200,
        verbose_name=_('Brand name of product'),
        null=True,
        blank=True,
    )

    #
    # Django methods
    #

    def get_absolute_url(self):
        """
        Returns the canonical URL to view this object.

        Since some names consist of only non-ascii characters (e.g. 감자깡), the
        resulting slug would be empty and no URL would match. In that case, use
        the regular URL with only the ID.
        """
        slug = slugify(self.name)
        if not slug:
            return reverse('nutrition:ingredient:view', kwargs={'id': self.id})
        else:
            return reverse('nutrition:ingredient:view',
                           kwargs={
                               'id': self.id,
                               'slug': slug
                           })

    def clean(self):
        """
        Do a very broad sanity check on the nutritional values according to
        the following rules:
        - 1g of protein: 4kcal
        - 1g of carbohydrates: 4kcal
        - 1g of fat: 9kcal

        The sum is then compared to the given total energy, with ENERGY_APPROXIMATION
        percent tolerance.
        """

        # Note: calculations in 100 grams, to save us the '/100' everywhere
        energy_protein = 0
        if self.protein:
            energy_protein = self.protein * ENERGY_FACTOR['protein']['kg']

        energy_carbohydrates = 0
        if self.carbohydrates:
            energy_carbohydrates = self.carbohydrates * ENERGY_FACTOR[
                'carbohydrates']['kg']

        energy_fat = 0
        if self.fat:
            # TODO: for some reason, during the tests the fat value is not
            #       converted to decimal (django 1.9)
            energy_fat = Decimal(self.fat * ENERGY_FACTOR['fat']['kg'])

        energy_calculated = energy_protein + energy_carbohydrates + energy_fat

        # Compare the values, but be generous
        if self.energy:
            energy_upper = self.energy * (
                1 + (self.ENERGY_APPROXIMATION / Decimal(100.0)))
            energy_lower = self.energy * (
                1 - (self.ENERGY_APPROXIMATION / Decimal(100.0)))

            if not ((energy_upper > energy_calculated) and
                    (energy_calculated > energy_lower)):
                raise ValidationError(
                    _('The total energy ({energy}kcal) is not the approximate sum of the '
                      'energy provided by protein, carbohydrates and fat ({energy_calculated}kcal '
                      '+/-{energy_approx}%)'.format(
                          energy=self.energy,
                          energy_calculated=energy_calculated,
                          energy_approx=self.ENERGY_APPROXIMATION)))

    def save(self, *args, **kwargs):
        """
        Reset the cache
        """

        super(Ingredient, self).save(*args, **kwargs)
        cache.delete(cache_mapper.get_ingredient_key(self.id))

    def __str__(self):
        """
        Return a more human-readable representation
        """
        return self.name

    def __eq__(self, other):
        """
        Compare ingredients based on their values, not like django on their PKs
        """

        logger.debug(
            'Overwritten behaviour: comparing ingredients on values, not PK.')
        equal = True
        if isinstance(other, self.__class__):
            for i in self._meta.fields:
                if (hasattr(self, i.name) and hasattr(other, i.name)
                        and (getattr(self, i.name, None) != getattr(
                            other, i.name, None))):
                    equal = False
        else:
            equal = False
        return equal

    def __hash__(self):
        """
        Define a hash function

        This is rather unnecessary, but it seems that newer versions of django
        have a problem when the __eq__ function is implemented, but not the
        __hash__ one. Returning hash(pk) is also django's default.

        :return: hash(pk)
        """
        return hash(self.pk)

    #
    # Own methods
    #
    def compare_with_database(self):
        """
        Compares the current ingredient with the version saved in the database.

        If the current object has no PK, returns false
        """
        if not self.pk:
            return False

        ingredient = Ingredient.objects.get(pk=self.pk)
        if self != ingredient:
            return False
        else:
            return True

    def send_email(self, request):
        """
        Sends an email after being successfully added to the database (for user
        submitted ingredients only)
        """
        try:
            user = User.objects.get(username=self.license_author)
        except User.DoesNotExist:
            return

        if self.license_author and user.email:
            translation.activate(
                user.userprofile.notification_language.short_name)
            url = request.build_absolute_uri(self.get_absolute_url())
            subject = _(
                'Ingredient was successfully added to the general database')
            context = {
                'ingredient': self.name,
                'url': url,
                'site': Site.objects.get_current().domain
            }
            message = render_to_string('ingredient/email_new.tpl', context)
            mail.send_mail(subject,
                           message,
                           settings.WGER_SETTINGS['EMAIL_FROM'], [user.email],
                           fail_silently=True)

    def set_author(self, request):
        if request.user.has_perm('nutrition.add_ingredient'):
            self.status = Ingredient.STATUS_ACCEPTED
            if not self.license_author:
                self.license_author = request.get_host().split(':')[0]
        else:
            if not self.license_author:
                self.license_author = request.user.username

            # Send email to administrator
            subject = _('New user submitted ingredient')
            message = _(
                """The user {0} submitted a new ingredient "{1}".""".format(
                    request.user.username, self.name))
            mail.mail_admins(subject, message, fail_silently=True)

    def get_owner_object(self):
        """
        Ingredient has no owner information
        """
        return False

    @property
    def energy_kilojoule(self):
        """
        returns kilojoules for current ingredient, 0 if energy is uninitialized
        """
        if self.energy:
            return Decimal(self.energy * 4.184).quantize(TWOPLACES)
        else:
            return 0
Exemple #3
0
class ExerciseImage(AbstractSubmissionModel, AbstractLicenseModel,
                    models.Model):
    """
    Model for an exercise image
    """

    objects = SubmissionManager()
    """Custom manager"""

    exercise = models.ForeignKey(Exercise,
                                 verbose_name=_('Exercise'),
                                 on_delete=models.CASCADE)
    """The exercise the image belongs to"""

    image = models.ImageField(
        verbose_name=_('Image'),
        help_text=_('Only PNG and JPEG formats are supported'),
        upload_to=exercise_image_upload_dir)
    """Uploaded image"""

    is_main = models.BooleanField(
        verbose_name=_('Main picture'),
        default=False,
        help_text=_("Tick the box if you want to set this image as the "
                    "main one for the exercise (will be shown e.g. in "
                    "the search). The first image is automatically "
                    "marked by the system."))
    """A flag indicating whether the image is the exercise's main image"""
    class Meta:
        """
        Set default ordering
        """
        ordering = ['-is_main', 'id']
        base_manager_name = 'objects'

    def save(self, *args, **kwargs):
        """
        Only one image can be marked as main picture at a time
        """
        if self.is_main:
            ExerciseImage.objects.filter(exercise=self.exercise).update(
                is_main=False)
            self.is_main = True
        else:
            if ExerciseImage.objects.accepted().filter(exercise=self.exercise).count() == 0 \
               or not ExerciseImage.objects.accepted() \
                            .filter(exercise=self.exercise, is_main=True)\
                            .count():
                self.is_main = True

        #
        # Reset all cached infos
        #
        for language in Language.objects.all():
            delete_template_fragment_cache('muscle-overview', language.id)
            delete_template_fragment_cache('exercise-overview', language.id)
            delete_template_fragment_cache('exercise-overview-mobile',
                                           language.id)
            delete_template_fragment_cache('equipment-overview', language.id)

        # And go on
        super(ExerciseImage, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """
        Reset all cached infos
        """
        super(ExerciseImage, self).delete(*args, **kwargs)

        for language in Language.objects.all():
            delete_template_fragment_cache('muscle-overview', language.id)
            delete_template_fragment_cache('exercise-overview', language.id)
            delete_template_fragment_cache('exercise-overview-mobile',
                                           language.id)
            delete_template_fragment_cache('equipment-overview', language.id)

        # Make sure there is always a main image
        if not ExerciseImage.objects.accepted() \
                .filter(exercise=self.exercise, is_main=True).count() \
           and ExerciseImage.objects.accepted() \
                .filter(exercise=self.exercise) \
                .filter(is_main=False) \
                .count():

            image = ExerciseImage.objects.accepted() \
                .filter(exercise=self.exercise, is_main=False)[0]
            image.is_main = True
            image.save()

    def get_owner_object(self):
        """
        Image has no owner information
        """
        return False

    def set_author(self, request):
        """
        Set author and status

        This is only used when creating images (via web or API)
        """
        if request.user.has_perm('exercises.add_exerciseimage'):
            self.status = self.STATUS_ACCEPTED
            if not self.license_author:
                self.license_author = request.get_host().split(':')[0]

        else:
            if not self.license_author:
                self.license_author = request.user.username

            subject = _('New user submitted image')
            message = _(
                'The user {0} submitted a new image "{1}" for exercise {2}.'
            ).format(request.user.username, self.name, self.exercise)
            mail.mail_admins(str(subject), str(message), fail_silently=True)
Exemple #4
0
class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
    """
    Model for an exercise
    """

    objects = SubmissionManager()
    """Custom manager"""

    category = models.ForeignKey(ExerciseCategory,
                                 verbose_name=_('Category'),
                                 on_delete=models.CASCADE)
    description = models.TextField(max_length=2000,
                                   verbose_name=_('Description'),
                                   validators=[MinLengthValidator(40)])
    """Description on how to perform the exercise"""

    name = models.CharField(max_length=200, verbose_name=_('Name'))
    """The exercise's name, with correct uppercase"""

    name_original = models.CharField(max_length=200,
                                     verbose_name=_('Name'),
                                     default='')
    """The exercise's name, as entered by the user"""

    muscles = models.ManyToManyField(Muscle,
                                     blank=True,
                                     verbose_name=_('Primary muscles'))
    """Main muscles trained by the exercise"""

    muscles_secondary = models.ManyToManyField(
        Muscle,
        verbose_name=_('Secondary muscles'),
        related_name='secondary_muscles',
        blank=True)
    """Secondary muscles trained by the exercise"""

    equipment = models.ManyToManyField(Equipment,
                                       verbose_name=_('Equipment'),
                                       blank=True)
    """Equipment needed by this exercise"""

    creation_date = models.DateField(_('Date'),
                                     auto_now_add=True,
                                     null=True,
                                     blank=True)
    """The submission date"""

    language = models.ForeignKey(Language,
                                 verbose_name=_('Language'),
                                 on_delete=models.CASCADE)
    """The exercise's language"""

    uuid = models.UUIDField(default=uuid.uuid4,
                            editable=False,
                            verbose_name='UUID')
    """
    Globally unique ID, to identify the exercise across installations
    """

    #
    # Django methods
    #
    class Meta:
        base_manager_name = 'objects'
        ordering = [
            "name",
        ]

    def get_absolute_url(self):
        """
        Returns the canonical URL to view an exercise
        """
        return reverse('exercise:exercise:view',
                       kwargs={
                           'id': self.id,
                           'slug': slugify(self.name)
                       })

    def save(self, *args, **kwargs):
        """
        Reset all cached infos
        """
        self.name = smart_capitalize(self.name_original)
        super(Exercise, self).save(*args, **kwargs)

        # Cached template fragments
        for language in Language.objects.all():
            delete_template_fragment_cache('muscle-overview', language.id)
            delete_template_fragment_cache('exercise-overview', language.id)
            delete_template_fragment_cache('equipment-overview', language.id)

        # Cached workouts
        for set in self.set_set.all():
            reset_workout_canonical_form(set.exerciseday.training_id)

    def delete(self, *args, **kwargs):
        """
        Reset all cached infos
        """

        # Cached template fragments
        for language in Language.objects.all():
            delete_template_fragment_cache('muscle-overview', language.id)
            delete_template_fragment_cache('exercise-overview', language.id)
            delete_template_fragment_cache('equipment-overview', language.id)

        # Cached workouts
        for set in self.set_set.all():
            reset_workout_canonical_form(set.exerciseday.training.pk)

        super(Exercise, self).delete(*args, **kwargs)

    def __str__(self):
        """
        Return a more human-readable representation
        """
        return self.name

    #
    # Own methods
    #

    @property
    def main_image(self):
        """
        Return the main image for the exercise or None if nothing is found
        """
        return self.exerciseimage_set.accepted().filter(is_main=True).first()

    @property
    def description_clean(self):
        """
        Return the exercise description with all markup removed
        """
        return bleach.clean(self.description, strip=True)

    def get_owner_object(self):
        """
        Exercise has no owner information
        """
        return False

    def send_email(self, request):
        """
        Sends an email after being successfully added to the database (for user
        submitted exercises only)
        """
        try:
            user = User.objects.get(username=self.license_author)
        except User.DoesNotExist:
            return
        if self.license_author and user.email:
            translation.activate(
                user.userprofile.notification_language.short_name)
            url = request.build_absolute_uri(self.get_absolute_url())
            subject = _(
                'Exercise was successfully added to the general database')
            context = {
                'exercise': self.name,
                'url': url,
                'site': Site.objects.get_current().domain
            }
            message = render_to_string('exercise/email_new.tpl', context)
            mail.send_mail(subject,
                           message,
                           settings.WGER_SETTINGS['EMAIL_FROM'], [user.email],
                           fail_silently=True)

    def set_author(self, request):
        """
        Set author and status

        This is only used when creating exercises (via web or API)
        """
        if request.user.has_perm('exercises.add_exercise'):
            self.status = self.STATUS_ACCEPTED
            if not self.license_author:
                self.license_author = request.get_host().split(':')[0]
        else:
            if not self.license_author:
                self.license_author = request.user.username

            subject = _('New user submitted exercise')
            message = _('The user {0} submitted a new exercise "{1}".').format(
                request.user.username, self.name)
            mail.mail_admins(str(subject), str(message), fail_silently=True)
Exemple #5
0
class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
    '''
    Model for an exercise
    '''

    objects = SubmissionManager()
    '''Custom manager'''

    category = models.ForeignKey(ExerciseCategory, verbose_name=_('Category'))
    description = models.TextField(max_length=2000,
                                   verbose_name=_('Description'),
                                   validators=[MinLengthValidator(40)])
    '''Description on how to perform the exercise'''

    name = models.CharField(max_length=200, verbose_name=_('Name'))

    muscles = models.ManyToManyField(Muscle,
                                     blank=True,
                                     verbose_name=_('Primary muscles'))
    '''Main muscles trained by the exercise'''

    muscles_secondary = models.ManyToManyField(
        Muscle,
        verbose_name=_('Secondary muscles'),
        related_name='secondary_muscles',
        blank=True)
    '''Secondary muscles trained by the exercise'''

    equipment = models.ManyToManyField(Equipment,
                                       verbose_name=_('Equipment'),
                                       blank=True)
    '''Equipment needed by this exercise'''

    creation_date = models.DateField(_('Date'),
                                     auto_now_add=True,
                                     null=True,
                                     blank=True)
    '''The submission date'''

    language = models.ForeignKey(Language, verbose_name=_('Language'))
    '''The exercise's language'''

    uuid = models.CharField(verbose_name='UUID',
                            max_length=36,
                            editable=False,
                            default=uuid.uuid4)
    '''
    Globally unique ID, to identify the exercise across installations
    '''

    #
    # Django methods
    #
    class Meta:
        ordering = [
            "name",
        ]

    def get_absolute_url(self):
        '''
        Returns the canonical URL to view an exercise
        '''
        return reverse('exercise:exercise:view',
                       kwargs={
                           'id': self.id,
                           'slug': slugify(self.name)
                       })

    def save(self, *args, **kwargs):
        '''
        Reset all cached infos
        '''

        super(Exercise, self).save(*args, **kwargs)

        # Cached objects
        cache.delete(cache_mapper.get_exercise_key(self))
        cache.delete(cache_mapper.get_exercise_muscle_bg_key(self))

        # Cached template fragments
        for language in Language.objects.all():
            delete_template_fragment_cache('muscle-overview', language.id)
            delete_template_fragment_cache('exercise-overview', language.id)
            delete_template_fragment_cache('exercise-overview-mobile',
                                           language.id)
            delete_template_fragment_cache('exercise-detail-header', self.id,
                                           language.id)
            delete_template_fragment_cache('exercise-detail-muscles', self.id,
                                           language.id)
            delete_template_fragment_cache('equipment-overview', language.id)

        # Cached workouts
        for set in self.set_set.all():
            reset_workout_canonical_form(set.exerciseday.training_id)

    def delete(self, *args, **kwargs):
        '''
        Reset all cached infos
        '''

        # Cached objects
        cache.delete(cache_mapper.get_exercise_key(self))
        cache.delete(cache_mapper.get_exercise_muscle_bg_key(self))

        # Cached template fragments
        for language in Language.objects.all():
            delete_template_fragment_cache('muscle-overview', language.id)
            delete_template_fragment_cache('exercise-overview', language.id)
            delete_template_fragment_cache('exercise-overview-mobile',
                                           language.id)
            delete_template_fragment_cache('exercise-detail-header', self.id,
                                           language.id)
            delete_template_fragment_cache('exercise-detail-muscles', self.id,
                                           language.id)
            delete_template_fragment_cache('equipment-overview', language.id)

        # Cached workouts
        for set in self.set_set.all():
            reset_workout_canonical_form(set.exerciseday.training.pk)

        super(Exercise, self).delete(*args, **kwargs)

    def __str__(self):
        '''
        Return a more human-readable representation
        '''
        return self.name

    #
    # Own methods
    #

    @property
    def main_image(self):
        '''
        Return the main image for the exercise or None if nothing is found
        '''
        return self.exerciseimage_set.accepted().filter(is_main=True).first()

    @property
    def description_clean(self):
        '''
        Return the exercise description with all markup removed
        '''
        return bleach.clean(self.description, strip=True)

    def get_owner_object(self):
        '''
        Exercise has no owner information
        '''
        return False

    def send_email(self, request):
        '''
        Sends an email after being successfully added to the database (for user
        submitted exercises only)
        '''
        try:
            user = User.objects.get(username=self.license_author)
        except User.DoesNotExist:
            return
        if self.license_author and user.email:
            translation.activate(
                user.userprofile.notification_language.short_name)
            url = request.build_absolute_uri(self.get_absolute_url())
            subject = _(
                'Exercise was successfully added to the general database')
            context = {'exercise': self.name, 'url': url}
            message = render_to_string('exercise/email_new.html', context)
            mail.send_mail(subject,
                           message,
                           EMAIL_FROM, [user.email],
                           fail_silently=True)

    def set_author(self, request):
        '''
        Set author and status

        This is only used when creating exercises (via web or API)
        '''
        if request.user.has_perm('exercises.add_exercise'):
            self.status = self.STATUS_ACCEPTED
            if not self.license_author:
                self.license_author = request.get_host().split(':')[0]
        else:
            if not self.license_author:
                self.license_author = request.user.username

            subject = _('New user submitted exercise')
            message = _(
                u'The user {0} submitted a new exercise "{1}".').format(
                    request.user.username, self.name)
            mail.mail_admins(six.text_type(subject),
                             six.text_type(message),
                             fail_silently=True)