PageRenderTime 810ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/apps/users/models.py

https://bitbucket.org/giussepi/meals-tracker
Python | 535 lines | 488 code | 16 blank | 31 comment | 0 complexity | 76ba4bde36f3d2984c466616f21e7715 MD5 | raw file
  1. #-*- coding: utf-8 -*-
  2. """ users models """
  3. from datetime import date, datetime, timedelta
  4. from chartit import DataPool, Chart
  5. from django.db import models
  6. from django.contrib.auth.models import User
  7. from django.core.exceptions import ObjectDoesNotExist
  8. from django.utils.translation import ugettext_lazy as _
  9. from .constants import PhysicalActivity, Gender, Goal
  10. class Base_Macronutrients(models.Model):
  11. """ abstract class that contains the base macronutrients fields """
  12. carbohydrates = models.FloatField(
  13. _(u'carbohydrates'), default=0, editable=False)
  14. protein = models.FloatField(
  15. _(u'proteins'), default=0, editable=False)
  16. fat = models.FloatField(_(u'grasas'), default=0, editable=False)
  17. calories = models.FloatField(_(u'calories'), default=0, editable=False)
  18. class Meta:
  19. """ model meta options """
  20. abstract = True
  21. def calculate_calories(self):
  22. """
  23. returns the calories based on the quantity of carbohydrates, protein
  24. and fat
  25. """
  26. return self.carbohydrates * 4 + self.protein * 4 + self.fat * 9
  27. # def save(self, *args, **kwargs):
  28. # """ recalculates the calories before saving """
  29. # self.calories = self.calculate_calories()
  30. # super(self.__class__, self).save(*args, **kwargs)
  31. class UserProfile(models.Model):
  32. """ stores additional user information """
  33. height = models.FloatField(_('height'), help_text=_(u'In meters.'))
  34. weight = models.FloatField(_('weight'), help_text=_(u'In kilos.'))
  35. physical_activity = models.CharField(
  36. _('physical activity'), max_length=1, choices=PhysicalActivity.CHOICES)
  37. goal = models.CharField(_('goal'), max_length=1, choices=Goal.CHOICES)
  38. gender = models.CharField(
  39. _('gender'), max_length=1, choices=Gender.CHOICES)
  40. birthday = models.DateField(_('birthday'))
  41. body_fat_percent = models.FloatField(
  42. _('body fat percent'), help_text=_("example: 13.1"))
  43. bmr = models.FloatField(help_text=_(u'Basal Metabollic Rate'))
  44. tee = models.FloatField(help_text=_(u'Total Energy Expenditure'))
  45. # rdi (recommended dietary intake)
  46. recalculate_rdis = models.BooleanField(
  47. _('Calculate RDIs'), default=True,
  48. help_text=_('Let the application calculate the recommended dietary '
  49. 'intake values')
  50. )
  51. rdi_carbohydrates = models.FloatField(
  52. _(u'carbohydrates'),
  53. help_text=_(u'Recommended dietary intake in grams.'))
  54. rdi_protein = models.FloatField(
  55. _(u'protein'), help_text=_(u'Recommended dietary intake in grams.'))
  56. rdi_fat = models.FloatField(
  57. _(u'fat'), help_text=_(u'Recommended dietary intake in grams.'))
  58. calories = models.FloatField(_('calories'))
  59. # user = models.ForeignKey(User, unique=True)
  60. user = models.OneToOneField(User, verbose_name=_('user'))
  61. class Meta:
  62. """ model meta options """
  63. ordering = ['user__last_name']
  64. verbose_name = _('User Profile')
  65. verbose_name_plural = _('User Profiles')
  66. def __unicode__(self):
  67. return u'%s' % self.user.get_full_name()
  68. @property
  69. def lean_body_mass(self):
  70. """ returns the lean body mass """
  71. return self.weight * (1 - (self.body_fat_percent/100))
  72. def calculate_bmr(self):
  73. """
  74. returns the calculated Basal Metabollic Rate using the Katch-McArdle
  75. formula.
  76. """
  77. return 370 + (21.6 * self.lean_body_mass)
  78. def get_bmr(self):
  79. """
  80. returns the Basal Metabollic Rate (if it doesn't exist it's
  81. calculated)
  82. """
  83. if not self.bmr:
  84. return self.calculate_bmr()
  85. return self.bmr
  86. def calculate_tee(self):
  87. """ returns the calculated Total Energy Expenditure """
  88. return self.get_bmr() * PhysicalActivity.get_activity_factor(
  89. self.physical_activity) * Goal.get_calories_factor(self.goal)
  90. def get_tee(self):
  91. """
  92. returns the Total Energy Expenditure (if it doesn't exist it's
  93. calculated)
  94. """
  95. if not self.tee:
  96. self.calculate_tee()
  97. return self.tee
  98. @property
  99. def protein_requirement(self):
  100. """
  101. returns the protein's requirement based on the physical activity and
  102. lean body mass
  103. # note: instead of weight the lean_body_mass used to be used here...
  104. """
  105. return PhysicalActivity.get_protein_requirement(
  106. self.physical_activity) * self.weight * \
  107. Goal.get_protein_factor(self.goal)
  108. @property
  109. def fat_requirement(self):
  110. """ returns the fat's requirement based on goal """
  111. return self.rdi_protein * Goal.get_fat_factor(self.goal)
  112. @property
  113. def carbs_requirement(self):
  114. """ returns the carbs' requirement based on goal """
  115. # # carbs calculated from the rest o calories
  116. # carbs = (
  117. # self.get_tee() - (self.rdi_protein * 4) - (self.rdi_fat * 9)
  118. # ) / 4
  119. return Goal.get_carbs_factor(self.goal) * self.weight
  120. def calculate_rdis(self):
  121. """
  122. Your lean body mass is just your body weight minus your body fat.
  123. # protein:
  124. factor proteing in grams for lean body mass
  125. upper limit: 0.82g/lb or 1.8/kg (very safe) 1g/lb (safe)
  126. to preserver or build: 0.82g/lb or 1.8/kg
  127. maintain lean body mass: 0.73g/lb?
  128. when cutting: no more than 0.82g/lb (to 2g/lb?)
  129. .5 - no sports or training
  130. .6 - jogger or light fitness training (light activity)
  131. .7 - moderate training, 3x per week
  132. .8 - moderate daily weight training or aerobics
  133. .9 - heavy weight training (very active)
  134. (Intense weight training 3-4 days per week or other
  135. daily high volume exercise)
  136. 1.0 - heavy weight training daily ( Elite Athlete or weight
  137. training >5 days per week)
  138. 1.5 - heavy weight training daily, plus cardio 3x per week
  139. # carbs
  140. # from http://www.labrada.com/blog/lees-corner/how-to-use-a-low-carb-diet-to-burn-fat-without-losing-muscle/
  141. bulking: 1.5 - 2 g/pound
  142. 2:1 ratio of carbs to protein?
  143. reduced carb diet to get ripped: 0.75 - 1 g of complex carb/pound
  144. # fat
  145. To get and stay lean multiply your daily grams of protein by 21%
  146. Grams of Protein * .21 = Your Daily Grams of fat
  147. volume training: factor = 0.42
  148. cutting: 17 and 28 percent of your total calorie intake
  149. """
  150. self.rdi_protein = self.protein_requirement
  151. self.rdi_fat = self.fat_requirement
  152. self.rdi_carbohydrates = self.carbs_requirement
  153. def calculate_calories(self):
  154. """
  155. returns the calories based on the quantity of carbohydrates, protein
  156. and fat
  157. """
  158. return self.rdi_carbohydrates * 4 + self.rdi_protein * 4 + \
  159. self.rdi_fat * 9
  160. def save(self, *args, **kwargs):
  161. """
  162. * Recalculates the calories, bmr and tee before saving
  163. * If recalculate_rdis is selected then recalculates the rdis
  164. """
  165. self.bmr = self.calculate_bmr()
  166. self.tee = self.calculate_tee()
  167. if self.recalculate_rdis:
  168. self.calculate_rdis()
  169. self.calories = self.calculate_calories()
  170. super(self.__class__, self).save(*args, **kwargs)
  171. def get_rdis(self):
  172. """ returns a tuple with the profile RDI values """
  173. return (self.rdi_protein, self.rdi_carbohydrates, self.rdi_fat)
  174. def pie_chart(self):
  175. """ Creates the Chart obj and returns it """
  176. user_data = \
  177. DataPool(
  178. series=[{
  179. 'options': {
  180. 'source': UserProfile.objects.filter(id=self.id),
  181. 'categories':['rdi_carbohydrates',
  182. 'rdi_protein', 'user',
  183. 'rdi_fat']
  184. },
  185. 'terms': ['rdi_carbohydrates', 'rdi_protein', 'user',
  186. 'rdi_fat']
  187. }]
  188. )
  189. cht = Chart(datasource=user_data,
  190. series_options=[{
  191. 'options':{
  192. 'type': 'column',
  193. 'stacking':False},
  194. 'terms':{
  195. 'user':['rdi_carbohydrates', 'rdi_protein',
  196. 'rdi_fat'], }
  197. }],
  198. chart_options={
  199. 'title': {'text': 'Column Chart of RDI (Recommended \
  200. Diertary Intake)'},
  201. 'xAxis': {
  202. 'title': {
  203. 'text': 'Base Macronutrients'}},
  204. 'yAxis': {
  205. 'title': {
  206. 'text': 'Grams'}}})
  207. return cht
  208. def get_latest_daily_consume(self):
  209. """
  210. returns the lastest daily consume or if it does not exist returns None
  211. """
  212. try:
  213. last_daily_consume = self.user.daily_consume_set.latest('id')
  214. except ObjectDoesNotExist:
  215. return None
  216. else:
  217. return last_daily_consume
  218. # def create_user_profile(sender, instance, created, **kwargs):
  219. # """ """
  220. # if created:
  221. # UserProfile.objects.create(user=instance)
  222. # post_save.connect(create_user_profile, sender=User)
  223. class Daily_Consume(Base_Macronutrients):
  224. """ stores the user daily consume """
  225. date = models.DateField(_('date'), default=date.today)
  226. user = models.ForeignKey('auth.User', verbose_name=_('user'))
  227. class Meta:
  228. """ model meta options """
  229. ordering = ['-date']
  230. verbose_name = _('Daily consume')
  231. verbose_name_plural = _('Daily consumes')
  232. def __unicode__(self):
  233. return u'%s' % self.date.strftime('%d/%m/%y')
  234. def update_values(self):
  235. """ udpates the base macronutrients values """
  236. self.carbohydrates = 0
  237. self.protein = 0
  238. self.fat = 0
  239. self.calories = 0
  240. for meal in self.meal_set.all():
  241. self.carbohydrates += meal.carbohydrates
  242. self.protein += meal.protein
  243. self.fat += meal.fat
  244. self.calories += meal.calories
  245. self.save()
  246. def save(self, *args, **kwargs):
  247. """ recalculates the calories before saving """
  248. #self.calories = self.calculate_calories()
  249. super(Daily_Consume, self).save(*args, **kwargs)
  250. def data_total_intake_chart(self):
  251. """ returns the rdi's values and the intake of the day """
  252. profile = self.user.userprofile
  253. return (
  254. (profile.rdi_protein, profile.rdi_carbohydrates, profile.rdi_fat),
  255. (self.protein, self.carbohydrates, self.fat),
  256. (profile.calories, self.calories)
  257. )
  258. def week_day(self, obj_id):
  259. """
  260. returns the fully weekday name and the month day of the Daily_Consume
  261. date with id = obj_id
  262. """
  263. return Daily_Consume.objects.get(id=obj_id).date.strftime('%A %d')
  264. def weekly_chart(self):
  265. """ Creates the Chart obj and returns it """
  266. monday = self.date - timedelta(days=(self.date.isocalendar()[2] - 1))
  267. sunday = monday + timedelta(days=6)
  268. user_data = \
  269. DataPool(
  270. series=[{
  271. 'options': {
  272. 'source': Daily_Consume.objects.filter(
  273. user=self.user).filter(date__gte=monday).filter(
  274. date__lte=sunday),
  275. },
  276. 'terms': ['protein',
  277. 'carbohydrates',
  278. 'fat',
  279. 'id'
  280. ]
  281. }]
  282. )
  283. cht = Chart(datasource=user_data,
  284. series_options=[{
  285. 'options':{
  286. 'type': 'line',
  287. 'stacking': False},
  288. 'terms':{
  289. 'id':['protein',
  290. 'carbohydrates',
  291. 'fat'
  292. ], }
  293. }],
  294. chart_options={
  295. 'title': {'text': "Current Week's Dietary Intake"},
  296. 'xAxis': {
  297. 'title': {
  298. 'text': 'Day'}},
  299. 'yAxis': {
  300. 'title': {
  301. 'text': 'Grams'}}},
  302. x_sortf_mapf_mts=(None, self.week_day, False)
  303. )
  304. return cht
  305. def meal_number(self, obj_id):
  306. """
  307. returns a string with the meal's time
  308. """
  309. return Meal.objects.get(id=obj_id).time.strftime('%I:%M %p')
  310. def day_consume_chart(self):
  311. """ Creates the Chart obj and returns it """
  312. user_data = \
  313. DataPool(
  314. series=[{
  315. 'options': {
  316. 'source': self.meal_set.all(),
  317. },
  318. 'terms': ['protein', 'carbohydrates', 'fat', 'id']
  319. }]
  320. )
  321. cht = Chart(datasource=user_data,
  322. series_options=[{
  323. 'options':{
  324. 'type': 'line',
  325. 'stacking': False},
  326. 'terms':{
  327. 'id':['protein',
  328. 'carbohydrates',
  329. 'fat',
  330. ], }
  331. }],
  332. chart_options={
  333. 'title': {'text': "Day's Dietary Intake"},
  334. 'xAxis': {
  335. 'title': {
  336. 'text': 'Meals'}},
  337. 'yAxis': {
  338. 'title': {
  339. 'text': 'Grams'}}},
  340. x_sortf_mapf_mts=(None, self.meal_number, False)
  341. )
  342. return cht
  343. def get_current_time():
  344. """ returns the current time """
  345. return datetime.now().time()
  346. class Meal(Base_Macronutrients):
  347. """ stores the macronutrients of one meal """
  348. time = models.TimeField(_('time'), default=get_current_time)
  349. daily_consume = models.ForeignKey(
  350. Daily_Consume, verbose_name=_('daily consume'))
  351. foods = models.ManyToManyField(
  352. 'foods.Food', through='MealFood', verbose_name=_('foods'))
  353. class Meta:
  354. """ meta class definitions """
  355. verbose_name = _('Meal')
  356. verbose_name_plural = _('Meals')
  357. def __unicode__(self):
  358. return u'%s %s' % (self.daily_consume.date.strftime(
  359. '%d/%m/%y'), self.time.strftime('%I:%M:%S %p'))
  360. def update_values(self):
  361. """
  362. recalculates the macronutrients and calories based on the food amount
  363. of food selected.
  364. Updates the values in the related daily_consume object
  365. """
  366. self.carbohydrates = 0
  367. self.protein = 0
  368. self.fat = 0
  369. self.calories = 0
  370. tmp = 0
  371. for mealfood in self.mealfood_set.all():
  372. # tmp = mealfood.grams / mealfood.food.grams
  373. if mealfood.grams:
  374. tmp = mealfood.grams / mealfood.food.grams
  375. else:
  376. tmp = (mealfood.presentation.grams / mealfood.food.grams)
  377. if mealfood.presentation_multiplier:
  378. tmp *= float(mealfood.presentation_multiplier)
  379. self.carbohydrates += tmp * mealfood.food.carbohydrates
  380. self.protein += tmp * mealfood.food.protein
  381. self.fat += tmp * mealfood.food.fat
  382. self.calories += tmp * mealfood.food.calories
  383. self.save()
  384. self.daily_consume.update_values()
  385. def meal_number(self, obj_id):
  386. """
  387. returns a string with the meal's time
  388. """
  389. return Meal.objects.get(id=obj_id).time.strftime('%I:%M %p')
  390. def day_consume_chart(self):
  391. """ Creates the Chart obj and returns it """
  392. user_data = \
  393. DataPool(
  394. series=[{
  395. 'options': {
  396. 'source': Meal.objects.filter(
  397. daily_consume=self.daily_consume),
  398. },
  399. 'terms': ['protein', 'carbohydrates', 'fat', 'id']
  400. }]
  401. )
  402. cht = Chart(datasource=user_data,
  403. series_options=[{
  404. 'options':{
  405. 'type': 'line',
  406. 'stacking': False},
  407. 'terms':{
  408. 'id':['protein',
  409. 'carbohydrates',
  410. 'fat',
  411. ], }
  412. }],
  413. chart_options={
  414. 'title': {'text': "Day's Dietary Intake"},
  415. 'xAxis': {
  416. 'title': {
  417. 'text': 'Meals'}},
  418. 'yAxis': {
  419. 'title': {
  420. 'text': 'Grams'}}},
  421. x_sortf_mapf_mts=(None, self.meal_number, False)
  422. )
  423. return cht
  424. def get_macronutrients_values(self):
  425. """
  426. returns the proteins, carbohydrates and fats of the current meal
  427. """
  428. return (self.protein, self.carbohydrates, self.fat)
  429. class MealFood(models.Model):
  430. """ intermediary table between Meal and Food """
  431. meal = models.ForeignKey(Meal, verbose_name=_('meal'))
  432. food = models.ForeignKey('foods.Food', verbose_name=_('food'))
  433. grams = models.FloatField(_('grams'), blank=True, null=True)
  434. presentation = models.ForeignKey(
  435. 'foods.Presentation', blank=True, null=True,
  436. verbose_name=_('presentation')
  437. )
  438. presentation_multiplier = models.DecimalField(
  439. _('presentation multiplier'),
  440. max_digits=4, decimal_places=2,
  441. help_text=_('Multiplies the presentation per X times'),
  442. blank=True, null=True
  443. )
  444. class Meta:
  445. """ meta class definitions """
  446. verbose_name = _('Meal-Food')
  447. verbose_name_plural = _('Meals-Foods')
  448. def save(self, *args, **kwargs):
  449. """
  450. saves the mealfood object and updates the macronutrients and calories
  451. values of the related meal
  452. """
  453. super(self.__class__, self).save(*args, **kwargs)
  454. self.meal.update_values()
  455. def get_html_options_presentations(self):
  456. """ returns the html just the options presentations of the food """
  457. options = "<option value=''>Select a presentation</option>"
  458. tpl = "<option value='{}'{}>{}</option>"
  459. if self.presentation:
  460. for presentation in self.food.presentation_set.all():
  461. if self.presentation == presentation:
  462. selected = " selected='selected'"
  463. else:
  464. selected = ""
  465. options += tpl.format(
  466. presentation.id, selected, presentation.name)
  467. else:
  468. options = self.food.get_html_options_presentations()
  469. return options