Array of foreign keys in Django Model - python

I'm not sure this is the best title, but I'm having trouble phrasing it simply. Basically, I'm creating a model that represents a business. This includes and address, operating hours, etc. It's the operating hours that are tripping me up. I have my address
class Address(models.Model):
--snip--
Business = models.ForeignKey(BusinessInfo)
So each business has one or more location addresses. I'm looking to do similar with Hours
class HoursOnDay(models.Model):
open = isOpen = models.BooleanField()
open = models.TimeField(null=True)
closed = models.TimeField(null=True)
What I want to enforce is that each business has to have an array of 7 HoursOnDay - one for each day of the week. I can't seem to stumble on the obvious, elegant way to do this. Is there a good way to model this in django?

use ManyToManyField:
class HoursOnDay(models.Model):
is_open = models.BooleanField()
open = models.TimeField(null=True)
closed = models.TimeField(null=True)
class Day(models.Model):
hours = models.ManyToManyField(HoursOnDay)
class Business(models.Model):
days = models.ManyToManyField(Day)
if you want to have limit for 7hours and 7days you can check
Limit number of model instances to be created

I have come across a similar problem (needed a bunch of fields for every hour of the day) and reached the following conclusion:
The most djangoish way to do this, is to not try and do anything smart. If you need a bunch of fields seven times, just copy paste the fields and give them different postfix or prefix. E.g.:
class Business(models.Model):
--snip--
monday_is_open = models.BooleanField()
monday_opens_at = models.TimeField(null=True)
monday_closes_at = models.TimeField(null=True)
tuesday_is_open = models.BooleanField()
tuesday_opens_at = models.TimeField(null=True)
tuesday_closes_at = models.TimeField(null=True)
wednesday_is_open = models.BooleanField()
wednesday_opens_at = models.TimeField(null=True)
wednesday_closes_at = models.TimeField(null=True)
...
sunday_is_open = models.BooleanField()
sunday_opens_at = models.TimeField(null=True)
sunday_closes_at = models.TimeField(null=True)
Of course Americans (and others?) would start with sunday, but you get the idea.
Following this approach greatly simplifies updating opening hours. This approach (and what you requested) fail to model changing opening hours from week to week, but that is probably not your need either.
Also, if you are concerned with schema normalization, get rid of the is_open, and just use xxx_opens_at__isnull=True, xxx_closes_at__isnull=True where you would have used xxx_is_open=True.
You can still do smart stuff with iteration across the weekdays in Business.clean().

Related

Django: How to access the previous model class instances while creating new instance of the same class?

I have a model in my django app like below:
models.py
class Profit(models.Model):
client = models.ForeignKey(Client, null=True, on_delete=models.CASCADE)
month = models.CharField(max_length=100)
amount = models.IntegerField()
total_profit = models.IntegerField()
Now, what I want to do is that whenever a new instance/object is created for this class, the user puts the month and the amount of profit for that month, But I want that it also calculates the total profit the user got up till the current profit, by adding all the profits that was being added in the past.
For example.
if the user is adding the profit for month April, then it add all the values in the amount field of previously added objects of (March, February, January and so on..) and put it in the field total_profit. So that the user can see how much total_profit he got at each new entry.
My views.py where I am printing the list of profits is given below:
views.py
class ProfitListView(ListView):
model = Profit
template_name = 'client_management_system/profit_detail.html'
context_object_name = 'profits'
# pk=self.kwargs['pk'] is to get the client id/pk from URL
def get_queryset(self):
user = get_object_or_404(Client, pk=self.kwargs['pk'])
return Profit.objects.filter(client=user)
Client is the another model in my models.py to which the Profit class is connected via ForeignKey
I also don't exactly know how to use window functions inside this view.
As stated in the comments one should generally not store things in the database that can be calculated from other data. Since that leads to duplication and then makes it difficult to update data. Although if your data might not change and this is some financial data one might store it anyway for record keeping purposes.
Firstly month as a CharField is not a very suitable field of yours for your schema. As firstly they are not easily ordered, secondly it would be better for you to work with a DateTimeField instead:
class Profit(models.Model):
month = models.CharField(max_length=100) # Remove this
made_on = models.DateTimeField() # A `DateTimeField` is better suited
amount = models.IntegerField()
total_profit = models.IntegerField()
Next since you want to print all the Profit instances along with the total amount you should use a Window function [Django docs] which will be ordered by made_on and we will also use a frame just in case that the made_on is same for two entries:
from django.db.models import F, RowRange, Sum, Window
queryset = Profit.objects.annotate(
total_amount=Window(
expression=Sum('amount'),
order_by=F('made_on').asc(),
frame=RowRange(end=0)
)
)
for profit in queryset:
print(f"Date: {profit.made_on}, Amount: {profit.amount}, Total amount: {profit.total_amount}")

Django manytomany m2m_changed script works, but how?

I have a model:
class Course(models.Model):
class Meta:
ordering = ['completion_order']
degree = models.ForeignKey(Degree, on_delete=models.CASCADE)
name = models.CharField(max_length=30, null=False, blank=False)
completion_order = models.PositiveSmallIntegerField(default=0,null=False, blank=False)
required = models.ManyToManyField('Task', default=1, blank=True, symmetrical=False)
And I have written a function to run on "m2m_changed" to automatically adjust the completion order to take into account any changes in prerequisites:
def required_changed(sender, instance, **kwargs):
degreecourses = Courses.objects.all().filter(degree=instance.degree)
for zcr in degreecourses: #resets the order for the all the degree's course instances
zcr.completion_order = 0
for cr in degreecourses: #assigns new completion_order values
if cr.required.count() is 0: # if no requirements sets 'completion_order' to 1
cr.completion_order = 1
else: #sets 'completion_order' to the increment of the highest (max) value of this courses's required course
mx = cr.required.all().aggregate(Max('completion_order'))
neworder = mx['completion_order__max'] + 1
cr.completion_order = neworder
cr.save()
This is run after any change in the m2m thus:
m2m_changed.connect(required_changed, sender=Course.required.through)
I originally set this up iterating through multiple times, but then realised it was unnecessary -my (novice) question is:
Can someone explain how this is able to resolve all of the "required" values in one pass, i.e. without having to iterate through the second loop multiple times?
I'm guessing it's to do with python being object oriented - but I'm new to object oriented programming, so whilst I'm pleased it works, I'm perplexed as to how it can assign the correct running order (i.e. even with many complex interdependencies between multiple courses) with seemingly only one pass?

Python - How to add two model fields in models.py in Django

I have a class in my models.py
class Inventory(models.Model):
date = models.DateField(("Date"), default=datetime.now)
product = models.ForeignKey(Product)
stock_in = models.IntegerField()
stock_out = models.IntegerField()
balance = models.IntegerField()
particulars = models.CharField(max_length=250)
Now I want to add some stocks in the balance. Using the stock_in values to add certain numbers to the balance of a specific product in the Inventory class. Using an UpdateView to it, so that I can just Update the stock_in field then adding that value to the balance.
I'm currently using this, I've tried couple of solution in the internet but to no avail.
#property
def total(self):
return self.stock_in + self.balance
There is no 'official' mechanism in Django to do this. Recently, some ideas of adding some official solution to the Django framework were discussed in this thread on the django-developers mailing list. It might serve as an inspiration for what solution is currently best for your case.
Your method works well for simple calculations. If the property gets more expensive to calculate, using #cached_property can help a bit if the value is used multiple times.
You can also rely on the database to compute these values by adding an annotation to the queryset. This requires defining a custom Manager:
class InventoryManager(models.Manager):
def get_queryset(self):
super().get_queryset().annotate(total=F('stock_in') + F('balance'))
class Inventory(models.Model):
date = models.DateField(("Date"), default=datetime.now)
product = models.ForeignKey(Product)
stock_in = models.IntegerField()
stock_out = models.IntegerField()
balance = models.IntegerField()
particulars = models.CharField(max_length=250)
objects = InventoryManager()
This will add a balance attribute to your Inventory model instances if they are retreived using the default manager.
The problem with this approach (like discussed in the linked django-developers thread) is what your expectations are when modals are changed locally.
For example, with the custom manager in place, if I were to change stock_in for a modal, the value of total would still be valid for the value of stock_in at the time of retrieving it from the database:
>> qs = Inventory.objects.filter(date__gte=date(2017, 12, 22))
>> inventory0 = qs[0]
>> print(inventory0.total, inventory0.stock_in, inventory.balance)
100, 50, 50
>> inventory.balance = 100
>> print(inventory0.total, inventory0.stock_in, inventory.balance)
100, 50, 100
Also, an model instance not fetched from the db at all wont have a total attribute:
>> inventory = Inventory(stock_in=20, balance=10)
>> inventory.total
AttributeError: 'Inventory' object has no attribute 'total'
Adding a __getattr__ method to your class might be a solution to this usecase, but will still result in incorrect answers with local changes.

Change query in Django based on day of week

First of all thank you for looking at my question.
I am looking for a way to store the day of week in a model, I have established that having a boolean for each day of the week in the model is likely the most simple approach. I had looked at using bitflags but was unsure again how to query this.
My model looks like the following
class CallForwardingRule(models.Model):
forward_to = models.CharField(max_length=255,null=False,blank=False)
start_time = models.TimeField(default=time(0,0))
end_time = models.TimeField(default=time(0,0))
active = models.BooleanField(default=True)
monday = models.BooleanField(default=False)
tuesday = models.BooleanField(default=False)
wednesday = models.BooleanField(default=False)
thursday = models.BooleanField(default=False)
friday = models.BooleanField(default=False)
saturday = models.BooleanField(default=False)
sunday = models.BooleanField(default=False)
My query is then like the following
CallForwardingRule.objects.filter(start_time__lte=time,end_time__gte=time)
What I need to do is alter the query depending on the current day, if the day is monday the query should look for a rule where boolean value monday=True
I hope I have been clear in my question, I am quite new to Django and Python.
Kind Regards
You can use a custom manager. Say
class TodayManager(models.Manager):
def get_queryset(self, *args, **kwargs):
today = self.weekday_as_string()
return super(TodayManager, self).get_queryset(*args, **kwargs).filter(
**{today: true})
def weekday_as_string(self):
# TODO
class CallForwardingRule(models.Model):
# your fields
of_today = TodayManager()
And query like this:
CallForwardingRule.of_today.filter(
start_time__lte=time,end_time__gte=time)
Read more about object managers here https://docs.djangoproject.com/en/1.8/topics/db/managers/
NOTE: If you don't intend for the user to be able to mix days in the same model instance, you should use an integer field with choices instead, as suggested in the comments.
You should either use an integer or a charfield for your dayofweek. Each of them may be used with choices (https://docs.djangoproject.com/en/1.8/ref/models/fields/#choices) which allows you to make it easier to translate the code of the day to its name.
Then, you just have to filter your queryset with this field.

Django-date incrementation in a list with ManyToManyField

New to django/programming, any help is greatly appreciated. I need help moving through a history of doctor appointments and selecting what immunizations were performed at each appointment, then creating a date when the immunization is due in the future (based on an immunization information table, which has the proper interval of immunizations and will increment from the visit date)
models.py
class Immunizations(models.Model):
immunization = models.CharField(max_length=100, null=True)
interval = models.CharField(max_length=5, null=True)**This should probably be an integer field, will change later
class Visit(models.Model):
patient = models.ForeignKey(Patients)
date_of_visit = models.DateField(null=True)
weight = models.CharField(max_length=5, null=True)
immunization = models.ManyToManyField(Immunizations)
timestamp = models.DateTimeField(auto_now_add=True, default=datetime.datetime.now())
active = models.BooleanField(default=True)
I have been reading the documentation and questions on SO all weekend, but am still very conflicted about what way to go through this.
What I want is:
Visit.date_of_visit1
Visit.immunization1, Visit.date_of_visit1 + Immunization.interval1
Visit.immunization2, Visit.date_of_visit1 + Immunization.interval2
Visit.date_of_visit2
Visit.immunization1, Visit.date_of_visit2 + Immunization.interval1
ETC
This could go on for years with each visit having different immunizations performed. I want to maintain a record of which immunization was performed and record the due date, even if that due date has passed.
views.py
def visit_profile(request, slug):
patient = Patients.objects.get(slug=slug)
try:
visit = Visit.objects.filter(patient_id=patient.id)
except:
return HttpResponseRedirect('/')
#Immunization Due Dates
visitdate = Visit.objects.get(patient_id=patient.id, active=1).date_of_visit
imm = Immunizations.objects.all()
visitimm = []
for immunization in imm:
due = Immunizations.objects.get(id= immunization.pk)
duedate = visitdate + timedelta(days=int(due.interval))
visitimm.append((due, duedate))
return render_to_response('patient.html',locals(), context_instance=RequestContext(request))
Need help with my views.py. The above works, but only at showing the active=1 visit information. I can't figure out how to modify/re-do to achieve what I want and be able to access the data in my template file. I've experimented with __in method, itertools, looping, etc. Can anyone provide the proper method/direction for doing this? I will go back and properly setup error catching once I can get the code to work. Thanks!
Yep, make interval an IntegerField or maybe rather a PositiveSmallIntegerField since it will never get a negative value nor a very huge number.
Careful, better don't mix plural and singular in model names, they affect the related names when you traverse your foreign keys which makes it a pain to debug, see here. I prefer to use only singulars.
Instead of:
visit = Visit.objects.filter(patient_id=patient.id)
You can simply type:
visit = Visit.objects.filter(patient=patient)
Try something like this
def visit_profile(request, slug):
patient = Patients.objects.get(slug=slug)
visitimm = []
# Looping over all active visit records of the patient in date order
for v in patient.visit_set
.filter(active=True).order_by('date_of_visit'):
# Looping over each visit's immunizations
for i in v.immunizations_set.all():
duedate = v.date_of_visit + timedelta(days=int(i.interval))
visitimm.append((i, duedate))
...

Categories

Resources