Using django modeling, how do you properly extend a model? - python

I'm an amateur developer who has been learning python in order to build my first full-stack project, which is an animal exchange site for a game I play. The animals can have up to 3 traits, but some of them have special ones. In the case of animals with special traits, I attempted to extend the base model and add additional traits using a notation someone suggested, but it doesn't seem to work. I know that technically I can just copypaste the base trait set and manually add them in, with a different model every time but that seems like it would bloat my code, and I'd like to be efficient about it. If you notice something else that could be improved, please let me know, thanks.
from django.db import models
#traits listed in class animal are base traits
class ANIMAL(models.Model):
MALE = 'MA'
FEMALE = 'FE'
SHINY = 'SHI'
NORMAL = 'NOR'
EGG = 'EGG'
CHILD = 'CHI'
ADOLESCENT = 'ADO'
ADULT = 'ADU'
ELDER = 'ELD'
BIGBONED = 'BIGB'
BUTTERFACE = 'BUTT'
CHARMED = 'CHAR'
CHATTY = 'CHAT'
CONSTIPATED = 'CONS'
CURVY = 'CURV'
EVIL = 'EVIL'
EXALTED = 'EXAL'
FORTUNATE = 'FORT'
FREAKOFNATURE = 'FREA'
FROSTBREATH = 'FROS'
FUSSYEATER = 'FUSS'
GENETICINFERIORITY = 'GINF'
GENETICINSTABILITY = 'GINS'
GENETICMUTATION = 'GENM'
GENIUS = 'GENI'
GIVER = 'GIVE'
GLISTENING = 'GLIS'
GOLDENGIFT = 'GOLD'
GOOD = 'GOOD'
GOODBREEDING = 'GOOB'
HANDSOME = 'HAND'
HYPERACTIVE = 'HYPE'
IMMUNE = 'IMMU'
INSANE = 'INSA'
JOVIAL = 'JOVI'
JOYFUL = 'JOYF'
LIMITEDEFFICIENCY = 'LIMI'
LITHE = 'LITH'
LUCKY = 'LUCK'
MYSTERIOUS = 'MYST'
NICEBUTDIM = 'NICE'
OLDATHEART = 'OLDA'
PERFECTED = 'PERF'
PLAIN = 'PLAI'
POISONOUSBREATH = 'POIS'
PRIZESPECIMAN = 'PRIZ'
PRODUCER = 'PROD'
RADIANT = 'RADI'
RAVENSWORN = 'RAVE'
REGULAR = 'REGU'
ROBUST = 'ROBU'
SHOCKBREATH = 'SHOC'
SICKLY = 'SICK'
SLOWPOKE = 'SLOW'
SMELLY = 'SMEL'
SPARKLING = 'SPAR'
STINGY = 'STIN'
STRESSED = 'STRE'
STRONGGENES = 'STRO'
STUDLY = 'STUD'
SULLEN= 'SULL'
SURLY = 'SURL'
TAKER = 'TAKE'
UNLUCKYFORSOME = 'UNLU'
VIRILE = 'VIRI'
YOUNGATHEART = 'YOUN'
NONE = 'NONE'
GENDER_CHOICES = [
(MALE, 'Male'),
(FEMALE, 'Female'),
]
AGE_CHOICES = [
(EGG, 'Egg'),
(CHILD, 'Child'),
(ADOLESCENT, 'Adolescent'),
(ADULT, 'Adult'),
(ELDER, 'Elder'),
]
SHINY_CHOICES = [
(SHINY, 'Shiny'),
(NORMAL, 'Normal'),
]
FIRST_TRAIT_CHOICES = [
(BIGBONED, 'Big Boned'),
(CONSTIPATED, 'Constipated'),
(FUSSYEATER, 'Fussy Eater'),
(GENETICINFERIORITY, 'Genetic Inferiority'),
(GENETICMUTATION, 'Genetic Mutation'),
(GIVER, 'Giver'),
(HANDSOME, 'Handsome'),
(HYPERACTIVE, 'Hyperactive'),
(JOVIAL, 'Jovial'),
(JOYFUL, 'Joyful'),
(LUCKY, 'Lucky'),
(OLDATHEART, 'Old at Heart'),
(PLAIN, 'Plain'),
(PRODUCER, 'Producer'),
(REGULAR, 'Regular'),
(ROBUST, 'Robust'),
(SICKLY, 'Sickly'),
(SLOWPOKE, 'Slowpoke'),
(SPARKLING, 'Sparkling'),
(STINGY, 'Stingy'),
(STRESSED, 'Stressed'),
(STUDLY, 'Studly'),
(SULLEN, 'Sullen'),
(SURLY, 'Surly'),
(VIRILE, 'Virile'),
(YOUNGATHEART, 'Young at Heart'),
]
SECOND_TRAIT_CHOICES = [
(NONE, 'None'),
(CHARMED, 'Charmed'),
(CHATTY, 'Chatty'),
(EVIL, 'Evil'),
(GENETICINSTABILITY, 'Genetic Instability'),
(GENETICMUTATION, 'Genetic Mutation'),
(GENIUS, 'Genius'),
(GIVER, 'Giver'),
(GLISTENING, 'Glistening'),
(GOOD, 'Good'),
(GOODBREEDING, 'Good Breeding'),
(HANDSOME, 'Handsome'),
(HYPERACTIVE, 'Hyperactive'),
(IMMUNE, 'Immune'),
(INSANE, 'Insane'),
(JOVIAL, 'Jovial'),
(JOYFUL, 'Joyful'),
(LUCKY, 'Lucky'),
(MYSTERIOUS, 'Mysterious'),
(PERFECTED, 'Perfected'),
(PRODUCER, 'Producer'),
(RAVENSWORN, 'Ravensworn'),
(ROBUST, 'Robust'),
(SMELLY, 'Smelly'),
(SPARKLING, 'Sparkling'),
(STRONGGENES, 'Strong Genes'),
(STUDLY, 'Studly'),
(VIRILE, 'Virile'),
]
THIRD_TRAIT_CHOICES = [
(NONE, 'None'),
(BUTTERFACE, 'Butterface'),
(CHARMED, 'Charmed'),
(CURVY, 'Curvy'),
(EXALTED, 'Exalted'),
(FORTUNATE, 'Fortunate'),
(FREAKOFNATURE, 'Freak of Nature'),
(GENETICINSTABILITY, 'Genetic Instability'),
(GENETICMUTATION, 'Genetic Mutation'),
(GIVER, 'Giver'),
(GLISTENING, 'Glistening'),
(GOLDENGIFT, 'Golden Gift'),
(GOODBREEDING, 'Good Breeding'),
(IMMUNE, 'Immune'),
(JOYFUL, 'Joyful'),
(LIMITEDEFFICIENCY, 'Limited Efficiency'),
(LITHE, 'Lithe'),
(LUCKY, 'Lucky'),
(MYSTERIOUS, 'Mysterious'),
(NICEBUTDIM, 'Nice But Dim'),
(PERFECTED, 'Perfected'),
(PRIZESPECIMAN, 'Prize Speciman'),
(PRODUCER, 'Producer'),
(RADIANT, 'Radiant'),
(SPARKLING, 'Sparkling'),
(STRONGGENES, 'Strong Genes'),
(STUDLY, 'Studly'),
(UNLUCKYFORSOME, 'Unlucky For Some'),
]
# max_length refers to the length of the characters stored in the database, not the length of the trait
gender_of_animal = models.CharField(
max_length= 6,
choices= GENDER_CHOICES,
default=MALE,
)
shiny_or_not = models.CharField(
max_length=3,
choices=SHINY_CHOICES,
default=NORMAL,
)
age_of_animal = models.CharField(
max_length=3,
choices=AGE_CHOICES,
default=ADOLESCENT,
)
first_trait = models.CharField(
max_length=4,
choices=FIRST_TRAIT_CHOICES,
default=SPARKLING,
)
second_trait = models.CharField(
max_length=4,
choices=SECOND_TRAIT_CHOICES,
default=NONE,
)
third_trait = models.CharField(
max_length=4,
choices=THIRD_TRAIT_CHOICES,
default=NONE,
)
#class Meta:
# abstract = True
class DRAGON(ANIMAL):
FIRST_TRAIT_CHOICES + ((FROSTBREATH, 'Frost Breath'), (POISONOUSBREATH, 'Poisonous Breath'), (SHOCKBREATH, 'Shock Breath',))
SECOND_TRAIT_CHOICES += ((FROSTBREATH, 'Frost Breath'), (POISONOUSBREATH, 'Poisonous Breath'), (SHOCKBREATH, 'Shock Breath',))
THIRD_TRAIT_CHOICES += ((FROSTBREATH, 'Frost Breath'), (POISONOUSBREATH, 'Poisonous Breath'), (SHOCKBREATH, 'Shock Breath',))

This is okay if you want your dragons to be separate from other animals, but I get the feeling you might not want this. Say you want to get all animals, you couldn't just do Animal.objects.all(), you would have to do Dragon.objects.all(), ... etc. for each one.
I would recommend the following restructuring:
Move all of your choices into separate models, there are just way too many in my opinion and they are both repetitive and hardcoded.
# Instead of
FIRST_TRAIT_CHOICES = [
(BIGBONED, 'Big Boned'),
... etc.
# do
class Trait(models.Model):
name = models.CharField(...
animal_types = models.ManyToManyField(...
# ^ m2m as many animals can have many trains
Note the animal_types field above - create another model that captures the type of animal, e.g. a Dragon:
class AnimalType(models.Model):
name = models.CharField(... # e.g. Dragon, etc.
Then attach this to your animal model - you can also do this for you traits. It makes it far more manageable and dynamic, also removing a lot of bloat from your model code.
class Animal(models.Model):
animal_type = models.ForeignKey(AnimalType, ...
first_trait = models.ForeignKey(Trait, ...
... etc.
Going back to your traits, you can create logic to ensure only certain animal types can have certain traits.
This makes your code much cleaner, you're pushing all of the data into the database which makes it more easily editable. It also means that if ever the traits or animal types change then you don't have to bulk update charfields, you just change the database entry.
Finally, you can get all dragons like this, without removing the ability to have dragons and other animals together in a queryset:
dragons = Animal.objects.filter(animal_type__name="dragon")

Related

How to create custom validation within custom model field type?

I am trying to make a school management system where super users who can add new students (and teachers later on) on the database have to also enter the student's courses. However, I want to use validation for different purposes:
Students need to take at least and a maximum of 1 option from the English and Math field (which does not need any further validation if left as it is)
Students need to take at least 1 science and a maximum of 2 --> Needs validation to ensure courses are not taken twice.
Students can pick any 3 courses from Science, Art, and Society, but cannot take the same courses twice.
I apologize if this question is silly or for any errors in the code as I started learning django and html 3 days ago to complete a school project. I read the django documentation in hopes for an answer but I couldn't find what I was looking for.
class Student(models.Model):
student_number = models.PositiveIntegerField()
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(max_length=100)
class English(models.TextChoices):
LANGLIT = 'LL', _('Language and literature')
LIT = 'L', _('Literature')
english = models.CharField(max_length=2, choices=English.choices, default=English.LANGLIT)
class Math(models.TextChoices):
AA = 'AA', _('Analysis & Approaches')
AI = 'AI', _('Analysis & Interpretation')
math = models.CharField(max_length=2, choices=Math.choices, default=Math.AA)
class Language(models.TextChoices):
FRENCH = 'F', _('French')
SPANISH = 'S', _('Spanish')
ARABIC = 'A', _('Arabic')
MANDARIN = 'M', _('Mandarin')
language = models.CharField(max_length=1, choices=Math.choices, default=Language.FRENCH)
class Science(models.TextChoices):
BIOLOGY = 'BIO', _('Biology')
CHEMISTRY = 'CHEM', _('Chemistry')
PHYSICS = 'PHY', _('Physics')
COMPUTERSCIENCE = 'CS', _('Computer Science')
DESIGNTECHNOLOGY = 'DT', _('Design Technology')
ESS = 'ESS', _('Environmental Systems and Societies')
science = models.CharField(max_length=4, choices=Science.choices, default=Science.BIOLOGY)
class Society(models.TextChoices):
MANAGEMENT = 'BM', _('Business Management')
ECONOMICS = 'ECON', _('Economics')
GEOGRAPHY = 'GEO', _('Geography')
GLOBALPOLITICS = 'GP', _('Global Politics')
HISTORY = 'HIS', _('History')
PSYCHOLOGY = 'PSYCH', _('Psychology')
class Art(models.TextChoices):
VISUALARTS = 'VA', _('Visual Arts')
MUSIC = 'MUS', _('Music'),
FILM = 'FILM', _('Film')
def __str__(self):
return f'{self.first_name} {self.last_name}'
you can use ValidationError and apply any logic you want
Example:
from django.core.exceptions import ValidationError
def validate_mail(value):
if "#mail.com" in value:
return value
else:
raise ValidationError("This field accepts mail only")
class MailModel(models.Model):
mail = models.CharField(
max_length = 200,
validators =[validate_mail]
)

How can I override a DjangoModelFormMutation field type in graphene?

I'm building a simple recipe storage application that uses the Graphene package for GraphQL. I've been able to use Django Forms so far very easily in my mutations, however one of my models fields is really an Enum and I'd like to expose it in Graphene/GraphQL as such.
My enum:
class Unit(Enum):
# Volume
TEASPOON = "teaspoon"
TABLESPOON = "tablespoon"
FLUID_OUNCE = "fl oz"
CUP = "cup"
US_PINT = "us pint"
IMPERIAL_PINT = "imperial pint"
US_QUART = "us quart"
IMPERIAL_QUART = "imperial quart"
US_GALLON = "us gallon"
IMPERIAL_GALLON = "imperial gallon"
MILLILITER = "milliliter"
LITER = "liter"
# Mass and Weight
POUND = "pound"
OUNCE = "ounce"
MILLIGRAM = "milligram"
GRAM = "gram"
KILOGRAM = "kilogram"
My Model:
class RecipeIngredient(TimeStampedModel):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name='ingredients')
direction = models.ForeignKey(RecipeDirection, on_delete=models.CASCADE, null=True, related_name='ingredients')
quantity = models.DecimalField(decimal_places=2, max_digits=10)
unit = models.TextField(choices=Unit.as_tuple_list())
My form:
class RecipeIngredientForm(forms.ModelForm):
class Meta:
model = RecipeIngredient
fields = (
'recipe',
'direction',
'quantity',
'unit',
)
My Mutation:
class CreateRecipeIngredientMutation(DjangoModelFormMutation):
class Meta:
form_class = RecipeIngredientForm
exclude_fields = ('id',)
I've created this graphene enum UnitEnum = Enum.from_enum(Unit) however I haven't been able to get graphene to pick it up. I've tried adding it to the CreateRecipeIngredientMutation as a regular field like unit = UnitEnum() as well as an Input class on that mutation. So far, the closest I've gotten is this Github issue from awhile ago. After playing around with the class in an iPython shell, I think I could just do CreateRecipeIngredientMutation.Input.unit.type.of_type = UnitEnum() but this feels awful.
I came up with a solution that works but is not pretty. I used the https://github.com/hzdg/django-enumfields package to help with this.
I created my own form field:
class EnumChoiceField(enumfields.forms.EnumChoiceField):
def __init__(self, enum, *, coerce=lambda val: val, empty_value='', **kwargs):
if isinstance(enum, six.string_types):
self.enum = import_string(enum)
else:
self.enum = enum
super().__init__(coerce=coerce, empty_value=empty_value, **kwargs)
And used it in my Django form. Then in my custom AppConfig I did this:
class CoreAppConfig(AppConfig):
name = 'myapp.core'
def ready(self):
registry = get_global_registry()
#convert_form_field.register(EnumChoiceField)
def convert_form_field_to_enum(field: EnumChoiceField):
converted = registry.get_converted_field(field.enum)
if converted is None:
raise ImproperlyConfigured("Enum %r is not registered." % field.enum)
return converted(description=field.help_text, required=field.required)
And finally in my schema:
UnitEnum = Enum.from_enum(Unit)
get_global_registry().register_converted_field(Unit, UnitEnum)
I really don't like this, but couldn't think of a better way to handle this. I came across this idea when searching down another graphene django issue here https://github.com/graphql-python/graphene-django/issues/481#issuecomment-412227036.
I feel like there has to be a better way to do this.

My django query is very slow in givig me data on terminal

I have a users table which has 3 types of users Student, Faculty and Club and I have a university table.
What I want is how many users are there in the specific university.
I am getting my desired output but the output is very slow.I have 90k users and the output it is generating it takes minutes to produce results.
My user model:-
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
from cms.models.masterUserTypes import MasterUserTypes
from cms.models.universities import Universities
from cms.models.departments import MasterDepartments
# WE ARE AT MODELS/APPUSERS
requestChoice = (
('male', 'male'),
('female', 'female'),
)
class Users(models.Model):
id = models.IntegerField(db_column="id", max_length=11, help_text="")
userTypeId = models.ForeignKey(MasterUserTypes, db_column="userTypeId")
universityId = models.ForeignKey(Universities, db_column="universityId")
departmentId = models.ForeignKey(MasterDepartments , db_column="departmentId",help_text="")
name = models.CharField(db_column="name",max_length=255,help_text="")
username = models.CharField(db_column="username",unique=True, max_length=255,help_text="")
email = models.CharField(db_column="email",unique=True, max_length=255,help_text="")
password = models.CharField(db_column="password",max_length=255,help_text="")
bio = models.TextField(db_column="bio",max_length=500,help_text="")
gender = models.CharField(db_column="gender",max_length=6, choices=requestChoice,help_text="")
mobileNo = models.CharField(db_column='mobileNo', max_length=16,help_text="")
dob = models.DateField(db_column="dob",help_text="")
major = models.CharField(db_column="major",max_length=255,help_text="")
graduationYear = models.IntegerField(db_column='graduationYear',max_length=11,help_text="")
canAddNews = models.BooleanField(db_column='canAddNews',default=False,help_text="")
receivePrivateMsgNotification = models.BooleanField(db_column='receivePrivateMsgNotification',default=True ,help_text="")
receivePrivateMsg = models.BooleanField(db_column='receivePrivateMsg',default=True ,help_text="")
receiveCommentNotification = models.BooleanField(db_column='receiveCommentNotification',default=True ,help_text="")
receiveLikeNotification = models.BooleanField(db_column='receiveLikeNotification',default=True ,help_text="")
receiveFavoriteFollowNotification = models.BooleanField(db_column='receiveFavoriteFollowNotification',default=True ,help_text="")
receiveNewPostNotification = models.BooleanField(db_column='receiveNewPostNotification',default=True ,help_text="")
allowInPopularList = models.BooleanField(db_column='allowInPopularList',default=True ,help_text="")
xmppResponse = models.TextField(db_column='xmppResponse',help_text="")
xmppDatetime = models.DateTimeField(db_column='xmppDatetime', help_text="")
status = models.BooleanField(db_column="status", default=False, help_text="")
deactivatedByAdmin = models.BooleanField(db_column="deactivatedByAdmin", default=False, help_text="")
createdAt = models.DateTimeField(db_column='createdAt', auto_now=True, help_text="")
modifiedAt = models.DateTimeField(db_column='modifiedAt', auto_now=True, help_text="")
updatedBy = models.ForeignKey(User,db_column="updatedBy",help_text="Logged in user updated by ......")
lastPasswordReset = models.DateTimeField(db_column='lastPasswordReset',help_text="")
authorities = models.CharField(db_column="departmentId",max_length=255,help_text="")
class Meta:
managed = False
db_table = 'users'
the query i am using which is producing the desired output but too sloq is:-
universities = Universities.objects.using('cms').all()
for item in universities:
studentcount = Users.objects.using('cms').filter(universityId=item.id,userTypeId=2).count()
facultyCount = Users.objects.using('cms').filter(universityId=item.id,userTypeId=1).count()
clubCount = Users.objects.using('cms').filter(universityId=item.id,userTypeId=3).count()
totalcount = Users.objects.using('cms').filter(universityId=item.id).count()
print studentcount,facultyCount,clubCount,totalcount
print item.name
You should use annotate to get the counts for each university and conditional expressions to get the counts based on conditions (docs)
Universities.objects.using('cms').annotate(
studentcount=Sum(Case(When(users_set__userTypeId=2, then=1), output_field=IntegerField())),
facultyCount =Sum(Case(When(users_set__userTypeId=1, then=1), output_field=IntegerField())),
clubCount=Sum(Case(When(users_set__userTypeId=3, then=1), output_field=IntegerField())),
totalcount=Count('users_set'),
)
First, an obvious optimization. In the loop, you're doing essentially the same query four times: thrice filtering for different userTypeId, and once without one. You can do this in a single COUNT(*) ... GROUP BY userTypeId query.
...
# Here, we're building a dict {userTypeId: count}
# by counting PKs over each userTypeId
qs = Users.objects.using('cms').filter(universityId=item.id)
counts = {
x["userTypeId"]: x["cnt"]
for x in qs.values('userTypeId').annotate(cnt=Count('pk'))
}
student_count = counts.get(2, 0)
faculty_count = counts.get(1, 0)
club_count = count.get(3, 0)
total_count = sum(count.values()) # Assuming there may be other userTypeIds
...
However, you're still doing 1+n queries, where n is number of universities you have in the database. This is fine if the number is low, but if it's high you need further aggregation, joining Universities and Users. A first draft I came with is something like this:
# Assuming University.name is unique, otherwise you'll need to use IDs
# to distinguish between different projects, instead of names.
qs = Users.objects.using('cms').values('userTypeId', 'university__name')\
.annotate(cnt=Count('pk').order_by('university__name')
for name, group in itertools.groupby(qs, lambda x: x["university__name"]):
print("University: %s" % name)
cnts = {g["userTypeId"]: g["cnt"] for g in group}
faculty, student, club = cnts.get(1, 0), cnts.get(2, 0), cnts.get(3, 0)
# NOTE: I'm assuming there are only few (if any) userTypeId values
# other than {1,2,3}.
total = sum(cnts.values())
print(" Student: %d, faculty: %d, club: %d, total: %d" % (
student, faculty, club, total))
I might've made a typo there, but hope it's correct. In terms of SQL, it should emit a query like
SELECT uni.name, usr.userTypeId, COUNT(usr.id)
FROM some_app_universities AS uni
LEFT JOUN some_app_users AS usr ON us.universityId = uni.id
GROUP BY uni.name, usr.userTypeId
ORDER BY uni.name
Consider reading documentation on aggregations and annotations. And be sure to check out raw SQL that Django ORM emits (e.g. use Django Debug Toolbar) and analyze how well it works on your database. For example, use EXPLAIN SELECT if you're using PostgreSQL. Depending on your dataset, you may benefit from some indexes there (e.g. on userTypeId column).
Oh, and on a side note... it's off-topic, but in Python it's a custom to have variables and attributes use lowercase_with_underscores. In Django, model class names are usually singular, e.g. User and University.

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