Generating models when I create one model (for a calendar app) - django

I've set up several models, as you'll see shortly in my code. These models are Era, Year, Month, Day, and Hour.
I'd like for, when I create an Era, it creates a set of Years, which in turn create Months, which in turn create Days, which create Hours. I'm sure I can figure out most of this if someone could just help me with the methodology with say the Era to Year step.
The goal is then to have the CurrentTime model able to step through all the other models (like a clock basically).
In case you're wondering, I'm going to try and make a simple web based game off this, that's why the values are a bit different from a regular calendar!
So here's my models.py file:
from django.db import models
# Create your models here.
DAY_LENGTH = 24 #1 day is 24 hours
MONTH_LENGTH = 24 #1 month is 24 days
MONTH_CHOICES = (
(1, 'January'),
(2, 'Febuary'),
(3, 'March'),
(4, 'April'),
(5, 'May'),
(6, 'June'),
(7, 'July'),
(8, 'August'),
(9, 'September'),
(10, 'October'),
(11, 'November'),
(12, 'December'),
(13, 'Extravember'),
(14, 'Othertober'),
)
DAY_CHOICES = (
(1, 'Monday'),
(2, 'Tuesday'),
(3, 'Wednesday'),
(4, 'Thursday'),
(5, 'Friday'),
(6, 'Saturday'),
)
YEAR_LENGTH = MONTH_CHOICES.length #1 year contains
ERA_LENGTH = 9999 #1 era is 9999 years #one of every month
NUMBER_OF_BLOCKS = 6 #Number of discreet actions programmable for a day
BLOCK_LENGTH = DAY_LENGTH / NUMBER_OF_BLOCKS
class Era(models.Model):
#Era number
value = models.AutoField()
#Name of the Era
name = models.CharField(max_length=50)
def __unicode__(self):
return "Era of " + self.name
class Year(models.Model):
#Year number
value = models.PositiveSmallIntegerField(max_value=9999)
#Era the year belongs to
era = models.ForeignKey('Era')
length = YEAR_LENGTH
def __unicode__(self):
return "Year " + self.value + self.era
class Month(models.Model):
#Should return name of month
value = models.PositiveSmallIntegerField(
choices=MONTH_CHOICES)
#Year that the month fits into
year = models.ForeignKey(Year)
def __unicode__(self):
return self.value " of " + self.year
class Day(models.Model):
#Should give the name of the day
name = models.PositiveSmallIntegerField(
choices = DAY_CHOICES)
#Month that the day belongs to
month = models.ForeignKey('Month')
#Day number, dependant on month length
value = models.PositiveSmallIntegerField(
max_value = month.length)
def __unicode__(self):
return self.name + ", day " + self.value + " of " + self.month
class Hour(models.Model):
value = models.PositiveSmallIntegerField(
max_value = DAY_LENGTH)
day = models.ForeignKey('Day')
def __unicode__(self):
return self.value + ":00, " + self.day
class CurrentTime(models.Model):
hour = ForeignKey('Hour')
day = ForeignKey('Day')
month = ForeignKey('Month')
year = ForeignKey('Year')
era = ForeignKey('Era')
def __unicode__(self): #!!will only work for current config!
return self.hour
I'm sure it's pretty messy... But in any case, please help me figure out my code! And any other help you'd like to give me, I appreciate too!

Storing a datastructure in 6 tables is overkill. You would not store a string "abc" in three different tables. So something in your design is, in my opinion, wrong.
Try instead to write your problem as simple as possible and post it to along with your solution to it here at SO.
There are sure alternatives to your implementation and import this
>>> import this
...
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
...
If you want to - against any advice ;-) - use your above stated Models, override the save method of your models.

Related

How to calculate numbers of days between two dates and subtract weekends DJANGO MODELS

hope you're all fine!
I have a model called Vacation and I'm struggling with one field: days_requested, this field is the number days from vacation_start and vacation_end, it works and gives me an integer as result. The problem I'm facing now is that I need to subtract the weekends (or not count them).
What I have:
vacation_start = '2022-05-20'
vacation_end = '2022-05-24'
days_requested = 5
What I'm trying to have:
vacation_start = '2022-05-20'
vacation_end = '2022-05-24'
days_requested = 3
#21/05 and 22/05 are weekends
Vacation Models:
class Vacation(models.Model):
department = models.ForeignKey(
'departments.Department', on_delete=models.SET_NULL, null=True)
responsible = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name='responsible_vacation')
status = models.CharField(
max_length=20, choices=STATUS_CHOICES_VACATIONS, default='open')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name='created_by_vacation')
vacation_start = models.DateField(blank=False)
vacation_end = models.DateField(blank=False)
days_requested = property(
lambda self: (self.vacation_end - self.vacation_start).days + 1
)
def __str__(self):
return str(self.created_by.first_name + ' ' + self.created_by.last_name)
I have tried:
days_requested = property(
lambda self: [(self.vacation_start + datetime.timedelta(days=i)).date()
for i in range(0, (self.vacation_end - self.vacation_start)) if (self.vacation_start + datetime.timedelta(days=i)).weekday() not in [5, 6].days()])
But I get the following error:
'datetime.timedelta' object cannot be interpreted as an integer
And as I perform operations with the amount of days asked I need to be able to get an integer.
Thank you all in advance.
UPDATE
class Model:
days_requested = models.IntegerField(blank=True,null=True)
def save(self, *args, **kwargs):
excluded = (6, 7)
days = 0
start_date =self.vacation_start
while start_date < self.vacation_end:
if start_date.isoweekday() not in excluded: #if you want to get only weekdays
days += 1
start_date+= timedelta(days=1)
self.days_requested=days
super(YourModel, self).save(*args, **kwargs)
After Elvin's answer I moved the hole logic to my view and set the logic inside form_valid function:
start = form.instance.vacation_start
end = form.instance.vacation_end
delta = end - start
excluded = (6, 7)
days = 0
for i in range(delta.days + 1):
day = start + datetime.timedelta(days=i)
if day.isoweekday() not in excluded:
days += 1
form.instance.days_requested = days
Thank you all.

Python Django: Count business days

I need to count business days between two dates. In addition, I must also remove the days listed in a separate table (holidays).
So far I have this code. It counts the days but does not remove the days from the separate table (holidays).
class Holidays(models.Model):
class Meta:
ordering = ['date']
date = models.DateField(null=True, verbose_name='Date')
class Situation(models.Model):
class Meta:
ordering = ['date_time_start']
date_time_start = models.DateTimeField(null=True, blank=False, verbose_name='Date/Time Start')
date_time_end = models.DateTimeField(null=True, blank=False, verbose_name='Date/Time End')
#property
def business_days(self):
holidays = Holidays.objects.values_list('date', flat=True)
oneday = datetime.timedelta(days=1)
dt = self.date_time_start.date()
total_days = 0
while (dt <= self.date_time_end.date()):
if not dt.isoweekday() in (6, 7) and dt not in holidays.values():
total_days += 1
dt += oneday
return total_days
Just a very quick tip for you, use numpy, and to be more exact:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.busday_count.html
it has everything that you need:
It counts business days between two dates and you can create a list of your own holidays, like so:
bd_holidays = ['2019-12-25', '2019-12-26']
bd_cal = np.busdaycalendar(holidays=bd_holidays)
after this you can go like:
count = np.busday_count(begindate, enddate, weekmask='1111100', busdaycal=bd_cal)

Django: Total birthdays each day for the next 30 days

I've got a model similar to this:
class Person(models.Model):
name = models.CharField(max_length=40)
birthday = DateTimeField() # their next birthday
I would like to get a list of the total birthdays for each day for the next 30 days. So for example, the list would look like this:
[[9, 0], [10, 3], [11, 1], [12, 1], [13, 5], ... #30 entries in list
Each list entry in the list is a date number followed by the number of birthdays on that day. So for example on the 9th of May there are 0 birthdays.
UPDATES
My db is sqlite3 - will be moving to postgres in the future.
from django.db.models import Count
import datetime
today = datetime.date.today()
thirty_days = today + datetime.timedelta(days=30)
birthdays = dict(Person.objects.filter(
birthday__range=[today, thirty_days]
).values_list('birthday').annotate(Count('birthday')))
for day in range(30):
date = today + datetime.timedelta(day)
print "[%s, %s]" % (date, birthdays.get(date, 0))
I would get the list of days and birthday count this way:
from datetime import date, timedelta
today = date.today()
thirty_days = today + timedelta(days=30)
# get everyone with a birthday
people = Person.objects.filter(birthday__range=[today, thirty_days])
birthday_counts = []
for date in [today + timedelta(x) for x in range(30)]:
# use filter to get only birthdays on given date's day, use len to get total
birthdays = [date.day, len(filter(lambda x: x.birthday.day == date.day, people))]
birthday_counts.append(birthdays)
Something like this --
from datetime import date, timedelta
class Person(models.Model):
name = models.CharField(max_length=40)
birthday = models.DateField()
#staticmethod
def upcoming_birthdays(days=30):
today = date.today()
where = 'DATE_ADD(birthday, INTERVAL (YEAR(NOW()) - YEAR(birthday)) YEAR) BETWEEN DATE(NOW()) AND DATE_ADD(NOW(), INTERVAL %S DAY)'
birthdays = Person.objects.extra(where=where, params=[days]).values_list('birthday', flat=True)
data = []
for offset in range(0, days):
i = 0
d = today + timedelta(days=offset)
for b in birthdays:
if b.day == d.day and b.month == d.month:
i += 1
data.append((d.day, i))
return data
print Person.upcoming_birthdays()
(Queryset of people with a birthday in the next X days)
Found cool solution for this!
For me it works!
from datetime import datetime, timedelta
import operator
from django.db.models import Q
def birthdays_within(days):
now = datetime.now()
then = now + timedelta(days)
# Build the list of month/day tuples.
monthdays = [(now.month, now.day)]
while now <= then:
monthdays.append((now.month, now.day))
now += timedelta(days=1)
# Tranform each into queryset keyword args.
monthdays = (dict(zip(("birthday__month", "birthday__day"), t))
for t in monthdays)
# Compose the djano.db.models.Q objects together for a single query.
query = reduce(operator.or_, (Q(**d) for d in monthdays))
# Run the query.
return Person.objects.filter(query)
But it get a list of persons that have a birthday in date range. You should change a bit.

Django - Add online columns in "select"

I dont know if this is the best way to resolve my problem, if isn't , tell me plz :)
I have this model :
class userTrophy(models.Model):
user = models.ForeignKey(userInfo)
platinum = models.IntegerField()
gold = models.IntegerField()
silver = models.IntegerField()
bronze = models.IntegerField()
level = models.IntegerField()
perc_level = models.IntegerField()
date_update = models.DateField(default=datetime.now, blank=True)
Now i want to retrieve one user info, but i want add 3 new "columns" online :
total = platinum + gold + silver + bronze
point = platinum * 100 + gold * 50 + silver * 25 + bronze * 10
and sort by "point", after sort, put a new column, with a sequencial number: rank (1-n).
Can i do this ( or part of this ) working only with the model ?
I am sure there are many ways to achieve this behavior. The one I am thinking of right now is a Custom Model Manager and transient model fields.
Your class could look like so:
from django.db import models
from datetime import datetime
class UserTrophyManager(models.Manager):
def get_query_set(self):
query_set = super(UserTrophyManager, self).get_query_set()
for ut in query_set:
ut.total = ut.platinum + ut.gold + ut.silver + ut.bronze
ut.points = ut.platinum * 100 + ut.gold * 50 + ut.silver * 25 + ut.bronze * 10
return query_set
class UserTrophy(models.Model):
user = models.CharField(max_length=30)
platinum = models.IntegerField()
gold = models.IntegerField()
silver = models.IntegerField()
bronze = models.IntegerField()
level = models.IntegerField()
perc_level = models.IntegerField()
date_update = models.DateField(default=datetime.now, blank=True)
total = 0
point = 0
objects = UserTrophyManager()
class Meta:
ordering = ['points']
So you can use the following and get total and point calculated:
user_trophies = userTrophy.objects.all()
for user_trophy in user_trophies:
print user_trophy.total
Here's the way I would do it. Add the columns 'total' and 'points' to your model, like this:
class UserTrophy(models.Model):
...
total = models.IntegerField()
points = models.IntegerField()
...
Override the save method for your model:
def save(self, *args, **kwargs):
# Compute the total and points before saving
self.total = self.platinum + self.gold + self.silver + self.bronze
self.points = self.platinum * 100 + self.gold * 50 + \
self.silver * 25 + self.bronze * 10
# Now save the object by calling the super class
super(UserTrophy, self).save(*args, **kwargs)
With total and points as first class citizens on your model, your concept of "rank" becomes just a matter of ordering and slicing the UserTrophy objects.
top_ten = UserTrophy.objects.order_by('-points')[:10]
You'll also want to make sure you have your fields indexed, so your queries are efficient.
If you don't like the idea of putting these fields in your model, you might be able to use the extra feature of Django query set objects to compute your total and points on the fly. I don't use this very often, so maybe someone else can put together an example.
Also, I recommend for you to read PEP 8 for Python coding conventions.
This is more of a followup question than an answer, but is it possible to do something like:
class userTrophy(models.Model):
... stuff...
def points(self):
self.gold + self.silver + self.bronze
then call something like object.points in a template. Im just curious if that is a possibility

Custom save method giving invalid tuple size error

I've been stuck on this likely very simple problem, but haven't gotten anywhere with it (newbie to Python and Django). I'm taking some user submitted data and using weights to calculate a score. Despite my best efforts, I'm getting the following when I submit the data via a form: "global name 'appearance' is not defined". I'm pretty sure my issue is in views.py, but I'm not 100% sure. Either a typecast error or just putting the calculation of the score in the wrong place. Any help is much appreciated. Here's my code:
Update: The error I'm receiving after changing my approach to using a custom save method is: "Invalid tuple size in creation of Decimal from list or tuple. The list or tuple should have exactly three elements.".
models.py
# Beer rating weights
APPEARANCE_WEIGHT = 0.15
AROMA_WEIGHT = 0.15
MOUTHFEEL_WEIGHT = 0.10
TASTE_WEIGHT = 0.25
TOTALPACKAGE_WEIGHT = 0.25
SERVING_TYPE = (
('0', 'Choose One'),
('Draft', 'Draft'),
('Bottle', 'Bottle'),
('Can', 'Can'),
)
SCORING = (
(0, ''),
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, '5'),
(6, '6'),
(7, '7'),
(8, '8'),
(9, '9'),
(10, '10'),
)
class Beerrating(models.Model):
beerrated = models.ForeignKey(Beer)
user = models.ForeignKey(User)
date = models.DateTimeField(auto_now_add=True)
servingtype = models.CharField(max_length=10, choices=SERVING_TYPE)
appearance = models.IntegerField(choices=SCORING, default=0)
aroma = models.IntegerField(choices=SCORING, default=0)
mouthfeel = models.IntegerField(choices=SCORING, default=0)
taste = models.IntegerField(choices=SCORING, default=0)
totalpackage = models.IntegerField(choices=SCORING, default=0)
comments = models.TextField()
overallrating = models.DecimalField(max_digits=4, decimal_places=2)
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.beerrated.beername)
def save(self):
if not self.id:
scoredappearance = self.appearance * APPEARANCE_WEIGHT,
scoredaroma = self.aroma * AROMA_WEIGHT,
scoredmouthfeel = self.mouthfeel * MOUTHFEEL_WEIGHT,
scoredtaste = self.taste * TASTE_WEIGHT,
scoredtotalpackage = self.totalpackage * TOTALPACKAGE_WEIGHT,
self.overallrating = (scoredappearance + scoredaroma +
scoredmouthfeel + scoredtaste + scoredtotalpackage)
super(Beerrating, self).save()
forms.py
class BeerReviewForm(ModelForm):
servingtype = forms.CharField(max_length=10,
label=u'Serving Type',
widget=forms.Select(choices=SERVING_TYPE)
)
totalpackage = forms.IntegerField(
label=u'Total Package',
widget=forms.Select(choices=SCORING)
)
class Meta:
model = Beerrating
exclude = ('beerrated', 'user', 'date', 'overallrating')
views.py
def beerreview(request, beer_id):
beer = get_object_or_404(Beer, id=beer_id)
if request.method == 'POST':
form = BeerReviewForm(request.POST)
if form.is_valid():
# Create review
beerrating = Beerrating(
beerrated = beer,
user = request.user,
servingtype = form.cleaned_data['servingtype'],
appearance = form.cleaned_data['appearance'],
scoredappearance = appearance * APPEARANCE_WEIGHT,
aroma = form.cleaned_data['aroma'],
scoredaroma = aroma * AROMA_WEIGHT,
mouthfeel = form.cleaned_data['mouthfeel'],
scoredmouthfeel = mouthfeel * MOUTHFEEL_WEIGHT,
taste = form.cleaned_data['taste'],
scoredtaste = taste * TASTE_WEIGHT,
totalpackage = form.cleaned_data['totalpackage'],
scoredtotalpackage = totalpackage * TOTALPACKAGE_WEIGHT,
comments = form.cleaned_data['comments'],
)
beerrating.save()
return HttpResponseRedirect('/beers/')
else:
form = BeerReviewForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('beer_review.html', variables)
The error message should specifically tell you the file and line number of the error, but your problem are these two lines in your views.py:
appearance = form.cleaned_data['appearance'],
scoredappearance = appearance * APPEARANCE_WEIGHT,
You are assuming the Python interpreter computes the value for appearance before you use it in the next argument... which is an incorrect assumption.
Define appearance before you create the model instance and your code should then work (or at least break on a different error).
In your save method, the lines:
scoredappearance = self.appearance * APPEARANCE_WEIGHT,
...
are all assigning a tuple, not the number you expect, to the variables. A tuple is basically an immutable list. The training comma on all those lines makes them tuples. What you want is:
scoredappearance = self.appearance * APPEARANCE_WEIGHT
...
Two other problems with your save function. First, because of your indentation, your super only gets called on an update -- which means you'll never be able to create this object!
Secondly, I'd recommend adding the variable arg lists to your save function. This means if it gets called with parameters, they get transparently passed onto the super.
Here's the rewritten function:
def save(self,*args,**kwargs):
if not self.id:
scoredappearance = self.appearance * APPEARANCE_WEIGHT
scoredaroma = self.aroma * AROMA_WEIGHT
scoredmouthfeel = self.mouthfeel * MOUTHFEEL_WEIGHT
scoredtaste = self.taste * TASTE_WEIGHT
scoredtotalpackage = self.totalpackage * TOTALPACKAGE_WEIGHT
self.overallrating = (scoredappearance + scoredaroma +
scoredmouthfeel + scoredtaste + scoredtotalpackage)
super(Beerrating, self).save(*args,**kwargs)
As a final note -- and if you've already done this, I apologize -- I'd really recommending working through a book like Learning Python. Python's a generally straightforward language -- but it has some subtle features (like tuples and variable argument lists) that will cause you trouble if you don't understand them.