Queryset from a ManyToMany relation - python

I'm creating a little calendar app in Django. I have two model classes; Calendar and Event. An event can be in multiple calendars. Because of this I'm using a ManyToMany relation.
This is my model
from django.db import models
class Calendar(models.Model):
title = models.CharField(max_length = 255)
def __unicode__(self):
return self.title
class Event(models.Model):
title = models.CharField(max_length = 255)
start_date = models.DateField()
end_date = models.DateField(blank = True, null = True)
location = models.CharField(blank = True, max_length = 255)
description = models.TextField(blank = True)
important = models.BooleanField(default = False)
calendar = models.ManyToManyField(Calendar)
How can I get a queryset with all events from a specific calendar?

You would use the .event_set attribute on an instance of a Calendar record. Like this:
# create two calendars
one = models.Calendar.objects.create(title='calendar one')
two = models.Calendar.objects.create(title='calendar two')
# attach event 1 to both calendars
event = models.Event.objects.create(title='event 1', start_date='2011-11-11')
one.event_set.add(event)
two.event_set.add(event)
# attach event 2 to calendar 2
two.event_set.add(models.Event.objects.create(title='event 2', start_date='2011-11-11'))
# get and print all events from calendar one
events_one = models.Calendar.objects.get(title='calendar one').event_set.all()
print [ event.title for event in events_one ]
# will print: [u'event 1']
# get and print all events from calendar two
events_two = models.Calendar.objects.get(title='calendar two').event_set.all()
print [ event.title for event in events_two ]
# will print: [u'event 1', u'event 2']
models.Calendar.objects.get(title='two').event_set.all()

Django automatically provides a way to access the related objects in a ManyToMany relationship:
events = my_calendar.events.all()
See the docs on many-to-many relationships.
If you don't already have a calendar instance, but just an ID or name, you can do the whole thing in one query:
events = Event.objects.filter(calendar__id=my_id)

mycalendar = Calendar.objects.get(id=1)
events = mycalendar.event_set.all()
Taken and modified from: http://docs.djangoproject.com/en/dev/topics/db/queries/#many-to-many-relationships

Related

How to compare what values two models/querysets share in a view for later display in the template?

I'm trying to compare all the things in ModelOne with ModelTwo, to check which things are or are not in one or the other model, then put this in the view context for display in the template.
class Things(model.Model):
name = models.CharField()
class ModelOne(models.Model):
things = models.ManyToManyField(Things)
class ModelTwo(models.Model):
things = models.ManyToManyField(Things)
How would you do this?
one_instance = ModelOne.objects.get(id=one_id)
two_instance = ModelTwo.objects.get(id=two_id)
one_thing_ids = set(one_instance.things.values_list("id", flat=True))
two_thing_ids = set(two_instance.things.values_list("id", flat=True))
shared_thing_ids = one_thing_ids & two_thing_ids
thing_ids_in_one_not_in_two = one_thing_ids - two_thing_ids
thing_ids_in_two_not_in_one = two_thing_ids - one_thing_ids
shared_things = Thing.objects.filter(id__in=shared_thing_ids)
You can then pass shared_things queryset into the template for display.
If your Thing model only has a name field and the names are unique we can simplify a little by altering the model:
class Things(model.Model):
name = models.CharField(unique=True)
or even:
class Things(model.Model):
name = models.CharField(primary_key=True, unique=True)
(in this case the db table will not have an id column, it's not needed)
Either way we can then eliminate the extra Thing query at the end:
one_instance = ModelOne.objects.get(id=one_id)
two_instance = ModelTwo.objects.get(id=two_id)
one_thing_names = set(one_instance.things.values_list("name", flat=True))
two_thing_names = set(two_instance.things.values_list("name", flat=True))
shared_thing_names = one_thing_names & two_thing_names
thing_names_in_one_not_in_two = one_thing_names - two_thing_names
thing_names_in_two_not_in_one = two_thing_names - one_thing_names
...and just pass sets of string names into the template.

donĀ“t create duplicated objects. django, python

I created a script to avoid creating duplicate objects but it still created the same objects when I run the command 3 times it creates them 3 times over and over again. I would like you to help me and know what is wrong with my code.
from django.core.management.base import BaseCommand
from jobs.models import Job
import json
from datetime import datetime
import dateparser
class Command(BaseCommand):
help = 'Set up the database'
def handle(self, *args: str, **options: str):
with open('static/newdata.json', 'r') as handle:
big_json = json.loads(handle.read())
for item in big_json:
if len(item['description']) == 0:
print('Not created. Description empty')
continue
dt = dateparser.parse(item['publication_date'])
existing_job = Job.objects.filter(
job_title = item['job_title'],
company = item['company'],
company_url = item['company_url'],
description = item['description'],
publication_date = dt,
salary = item['salary'],
city = item['city'],
district = item['district'],
job_url = item['job_url'],
job_type = item['job_type'],
)
if existing_job.exists() is True:
print('This Job already exist')
else:
Job.objects.create(
job_title = item['job_title'],
company = item['company'],
company_url = item['company_url'],
description = item['description'],
publication_date = dt,
salary = item['salary'],
city = item['city'],
district = item['district'],
job_url = item['job_url'],
job_type = item['job_type'],
)
self.stdout.write(self.style.SUCCESS('added jobs!'))
Have you tried using built-in field validation unique=True?
https://docs.djangoproject.com/en/3.1/ref/models/fields/#unique
try
if existing_job.exists():
instead of
if existing_job.exists() is True:
because .exists() returns boolean itself
Have you tried using unique_together without the publication_date field? Docs
# models.py
class Job(models.Model):
# Your fields here...
class Meta:
unique_together = [[
'job_title',
'company',
'company_url',
'description',
'salary',
'city',
'district',
'job_url',
'job_type'
]]
dt = dateparser.parse(item['publication_date'])
new_date = date(dt.year, dt.month, dt.day)
here was the problem. I am scraping the publication date in this format ('1 Week Ago') and then I was changing to a date and time format. and when I run the script again the time of the conversion is a different time. so that's why the job is created again. because is not the same because the time creation

Override ChoiceField choice attribute with for loop in Django views

I m trying to override ChoiceField in forms in which i can loop through specific object in my views,
But i Failed cause i only get in the template form only the last item in the list..
need some help to get all the choices i need from this object.
models.py
class TourPackageBuyer(models.Model):
tour = models.ForeignKey(TourPackage, on_delete=models.CASCADE, null =True) production
number_choice = [(i,i) for i in range(6)]
number_choice_2 = [(i,i) for i in range(18)]
number_choice_3 = [(i,i) for i in range(60)]
user = models.CharField(settings.AUTH_USER_MODEL, max_length=200)
num_of_adults = models.PositiveIntegerField(default=0, choices= number_choice_2, null=True)
num_of_children = models.PositiveIntegerField(default=0, choices= number_choice_3, null=True)
hotel = models.ManyToManyField(PackageHotel, blank=True)### thats the field
forms.py
class TourPackageBuyerForm(ModelForm):
class Meta:
model = TourPackageBuyer
date = datetime.date.today().strftime('%Y')
intDate = int(date)
limitDate = intDate + 1
YEARS= [x for x in range(intDate,limitDate)]
# YEARS= [2020,2021]
Months = '1',
# fields = '__all__'
exclude = ('user','tour','invoice','fees', 'paid_case')
widgets = {
'pickup_date': SelectDateWidget(empty_label=("Choose Year", "Choose Month", "Choose Day")),
'hotel': Select(),
# 'pickup_date': forms.DateField.now(),
}
hotel = forms.ChoiceField(choices=[]) ### Thats the field i m trying to override
views.py
def TourPackageBuyerView(request, tour_id):
user = request.user
tour = TourPackage.objects.get(id=tour_id)
tour_title = tour.tour_title
hotels = tour.hotel.all()
form = TourPackageBuyerForm(request.POST or None, request.FILES or None)
### im looping through specific items in the model in many to many field
for h in hotels:
form.fields['hotel'].choices = (h.hotel, h.hotel), ### when this loop it just give the last item in the form in my template!!
You are reassigning the value of choices every time through the loop, so you'll only get the last value you assign once the loop is finished.
You can fix this by replacing this:
for h in hotels:
form.fields['hotel'].choices = (h.hotel, h.hotel),
With this list comprehension:
form.fields['hotel'].choices = [(h.hotel, h.hotel) for h in hotels]
or if you want a tuple as output you can do:
form.fields['hotel'].choices = tuple((h.hotel, h.hotel) for h in hotels)

How do I display Django data from a related model of a related model?

I am trying to display data from several models that are related together through a QuerySet. My ultimate goal is to display some information from the Site model, and some information from the Ppack model, based on a date range filter of the sw_delivery_date in the Site model.
Here are my models:
class Site(models.Model):
mnemonic = models.CharField(max_length = 5)
site_name = models.CharField(max_length = 100)
assigned_tech = models.ForeignKey('Person', on_delete=models.CASCADE, null = True, blank = True)
hw_handoff_date = models.DateField(null = True, blank = True)
sw_delivery_date = models.DateField(null = True, blank = True)
go_live_date = models.DateField(null = True, blank = True)
web_url = models.CharField(max_length = 100, null = True, blank = True)
idp_url = models.CharField(max_length = 100, null = True, blank = True)
def __str__(self):
return '(' + self.mnemonic + ') ' + self.site_name
class Ring(models.Model):
ring = models.IntegerField()
def __str__(self):
return "6." + str(self.ring)
class Ppack(models.Model):
ppack = models.IntegerField()
ring = models.ForeignKey('Ring', on_delete=models.CASCADE)
def __str__(self):
return str(self.ring) + " pp" + str(self.ppack)
class Code_Release(models.Model):
Inhouse = 'I'
Test = 'T'
Live = 'L'
Ring_Location_Choices = (
(Inhouse, 'Inhouse'),
(Test, 'Test'),
(Live, 'Live'),
)
site_id = models.ForeignKey('Site', on_delete=models.CASCADE)
type = models.CharField(max_length = 1, choices = Ring_Location_Choices, blank = True, null = True)
release = models.ForeignKey('Ppack', on_delete=models.CASCADE)
def __str__(self):
return "site:" + str(self.site_id) + ", " + self.type + " = " + str(self.release)
If I use the following,
today = datetime.date.today()
future = datetime.timedelta(days=60)
new_deliveries = Site.objects.select_related().filter(sw_delivery_date__range=[today, (today + future)])
I can get all of the objects in the Site model that meet my criteria, however, because there is no relation from Site to Code_Release (there's a one-to-many coming the other way), I can't get at the Code_Release data.
If I run a for loop, I can iterate through every Site returned from the above query, and select the data from the Code_Release model, which allows me to get the related data from the Ppack and Ring models.
site_itl = {}
itl = {}
for delivery in new_deliveries:
releases = Code_Release.objects.select_related().filter(site_id = delivery.id)
for rel in releases:
itl[rel.id] = rel.release
site_itl[delivery.id] = itl
But, that seems overly complex to me, with multiple database hits and possibly a difficult time parsing through that in the template.
Based on that, I was thinking that I needed to select from the Code_Release model. That relates back to both the Site model and the Ppack model (which relates to the Ring model). I've struggled to make the right query / access the data in this way that accomplishes what I want, but I feel this is the right way to go.
How would I best accomplish this?
You can use RelatedManager here. When you declare ForeignKey, Django allows you to access reverse relationship. To be specific, let's say that you have multiple code releases that are pointing to one specific site. You can access them all via site object by using <your_model_name_lowercase>_set attribute. So in your case:
site.code_release_set.all()
will return QuerySet of all code release objects that have ForeignKey to object site
You can access the Releases from a Site object. First, you can put a related_name to have a friendly name of the reverse relation between the models:
site_id = models.ForeignKey('Site', on_delete=models.CASCADE, related_name="releases")
and then, from a Site object you can make normal queries to Release model:
site.releases.all()
site.releases.filter(...)
...

Filter Generic Foreign Key

Is there a more "Python/Django" way to query/filter objects by generic foreign key? I'm trying to get all FullCitation objects for a particular software, where is_primary is True.
I know I can't do this but I want to do something like this:
ct_supported = ContentType.objects.get(app_label="supportedprogram", model="software")
primary_citations = FullCitation.objects.filter(content_type__name=ct_supported, object_id__in='', is_primary=True)
models.py
class FullCitation(models.Model)
# the software to which this citation belongs
# either a supported software program or a non-supported software program
limit = models.Q(app_label = 'myprograms', model = 'supportedprogram') | models.Q(app_label = 'myprograms', model = 'nonsupportedprogram')
content_type = models.ForeignKey(ContentType), limit_choices_to = limit, )
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
is_primary = models.BooleanField(help_text="Is this the Primary Citation for the software program?")
class NonSupportedProgram(models.Model):
title = models.CharField(max_length=256, blank = True)
full_citation = generic.GenericRelation('FullCitation')
class SupportedProgram(models.Model):
title = models.CharField(max_length=256, blank = True)
full_citation = generic.GenericRelation('FullCitation')
# and a bunch of other fields.....
views.py # My current attempt
primary_citations = []
sw_citations = sw.full_citations.all()
for x in sw_citations:
if x.is_primary:
primary_citations.append(x)
Comprehensions should be a last resort for filtering QuerySets. Far better to let them remain as QuerySets as long as you can. I think this is what you're looking for:
ct_supported = ContentType.objects.get_for_model(SupportedProgram))
primary_citations = FullCitation.objects.filter(content_type=ct_supported, is_primary=True)
Updated:
If you want to filter for a specific SupportedProgram instance, do this:
my_supported = SupportedProgram.objects.get(id=instance_id_goes_here)
ct_supported = ContentType.objects.get_for_model(SupportedProgram))
primary_citations = FullCitation.objects.filter(content_object=my_supported, content_type=ct_supported, is_primary=True)

Categories

Resources