I have the following database tables:
class Story(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group, blank=True, null=True)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
location = models.CharField(max_length=100)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
exp_text = models.TextField()
category = models.ForeignKey(Category, blank=True, null=True)
def __unicode__(self):
return self.title
class Comment(models.Model):
user = models.ForeignKey(User)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
emailOnReply = models.NullBooleanField(blank=True, null=True)
comment_text = models.TextField()
story = models.ForeignKey(Story)
def __unicode__(self):
return self.comment_text
When i have created an object of Comment and need to access the title of a story i would expect to be able to do this:
c = Comment.objects.all()
for com in c:
com.comment_set.title
but django says that comment_set isnt defined. In the documentation it says that if you are tring to reach a field in the other database table where the foreign is not defined you can use the _set method where the word before it is the name of the class.
After trying different ways i found that this works:
c = Comment.objects.all()
for com in c:
com.story.title
Since the foreign key is defined in Comment i dont see how that can work, but it does, and why doesnt the _set work. Since im working on an object in the model where the foreign key is defined and i need to go to the story then i would need to use _set according to the docs...when working with a story objects im able to refer straigt to the comment model when defining the related_name property so i dont need _set there either.. why doesnt _set work here?
Update:
I now got the relationship working backwards when using a story object and refering to the comment class by:
s = Story.objects.all()
for st in s:
print st.comment_set.all()
I was using st.story_set instead of st.comment_set but i still find it weird that this works:
c = Comment.objects.all()
for com in c:
print com.story.title
When trying to work from the story object i dont have any foreign key to the comment table (only a related name on the foreign key in the comment table) so i dont get the same access it seems.
Actually everything is working as expected.
Comment object doesn't have a comment foreign key, it has a "story" foreign key.
comments are "pointing" to stories. as such - a comment only has one story, but a story may have a "set" of comments.
This is why st.comment_set works - because it "looks backward" to the comments pointing to it, while a comment just points directly to the story it is related to (i.e. com.story).
if you are interested in understanding why that works - see here:
https://docs.djangoproject.com/en/dev/topics/db/queries/#how-are-the-backward-relationships-possible
that's part of what makes Django's ORM so cool...
Following relationships in Django is a bit implicit. Here are simplified versions of your models:
class Story(models.Model): # instances have access to Manager comment_set (backward)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
class Comment(models.Model): # has access to Story (forward)
comment_text = models.TextField()
story = models.ForeignKey(Story) # stored in database as story_id, which refers to (implicit) id column in Story
Your database will look like this (unless you specify otherwise):
Story (table)
id # automatically inserted by Django, unless you specify otherwise
title
description
Comment (table)
id # automatically inserted by Django, unless you specify otherwise
comment_text
story_id # points back to id in Story table
FORWARD
Instances of Comment have access to Story by matching Comment's story_id column to Story's id column (the latter being implicit to Django tables and not in your model but definitely in your database table, unless you have specified otherwise).
>>> c = Comment.objects.get(comment_text='hello, world')
>>> c.story.title # c => story_id => row in Story where id == c.story_id
'first post'
BACKWARD
Comment has a ForeignKey pointing back to Story, so instances of Story have access to a Manager named comment_set that can follow relationships from Story to Comment.
>>> s = Story.objects.get(title='first post')
>>> s.comment_set.comment_text # accesses Comment model from Story instance
'hello, world'
Or if you want to iterate over all comment_sets as you mention in your comment, try this instead:
>>> s = Story.objects.get(title='first post') # returns a single, non-iterable query object
>>> for story in s.comment_set.all(): # each comment_set object can have more than one item
print story.comment_text # story does not have a comment_set attribute
'hello, world' # my comment set here just happens to have one item
UPDATE
Or, based upon your comment, you want to take the iteration to one level higher, try the following:
>>> s = Story.objects.all()
>>> for story in s: # iterate over top level
>>> for row in story.comment_set.all():
>>> print row.comment_text # again, row does not have a comment_set attribute
'hello, world' # again, my comment set has only one item
Related
This is my first time working with Django and while working I have encountered with a confusion to create a particular statement in views that leads to my desired output. I have created a model 'Parents' which has data of a specific student (Foreign Key), and I am confused to access that student id for further process like working with Attendance, or Results of that specific student. Below are necessary codes and my trial to fetch data.
Models.py
class Students(models.Model):
id = models.AutoField(primary_key=True)
admin = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
gender = models.CharField(max_length=50)
address = models.TextField()
course_id = models.ForeignKey(Courses, on_delete=models.DO_NOTHING, default=1)
session_year_id = models.ForeignKey(SessionYearModel, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = models.Manager()
def __str__(self):
return self.admin.first_name + " " + self.admin.last_name
class Parents(models.Model):
id = models.AutoField(primary_key=True)
admin = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
gender = models.CharField(max_length=50)
**student = models.ForeignKey(Students, on_delete=models.CASCADE)**
relation = models.CharField(max_length=255)
address = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = models.Manager()
def __str__(self):
return self.admin.first_name + " " + self.admin.last_name
Here I have two models, Students model has all information regarding student and the other model is Parent model which has parent information with its specific student id.
Below is the views file code where I am trying to fetch student id of currently logged in parent,
def HOME(request):
stud_data = Parents.objects.filter(student__id = request.user.id)
print(stud_data)
return None
At the time of page reload, I am able to get an empty QuerySet[] as it is not able to find the id.
Kindly help me finding the solution to this problem, so that I can continue thinking about the development.
Thanks :)
As you mentioned here, you are looking for Student data for currently logged in Parent. Hence you can look for Student data directly from Parent object. Like this:
stud_object = request.user.parent.student
This relation works because Parent has a OneToOne relation with CustomUser (I assume Authentication's custom User model), hence you should get Parent object from request.user.parent (reverse relation for OneToOne). Also, student field is a ForeignKey of Parent model.
Addionally, I think the relation between Parent and Student should be ManyToMany, because a Student can have multiple parents and one parent can have multiple students (or children).
There are two possibilities:
The View code that you have attached should be returning stud_data not None, but I am assuming that you know this and this current state of the code is just for debugging purposes.
The request.user.id contains a value that doesn't belong to any student's ID in the database. As you are using filter, it's not going to complain about it and just return you an empty QuerySet. I'd suggest using the get() filter here which raises the DoesNotExist exception and would help in debugging as well.
def home(request):
stud_data = Parents.objects.get(student__id = request.user.id)
return stud_data
Hope it helps!
Best of luck with your new journey!
I am a new user of Django, and I am trying to figure out how to created a model which can support many kind (type) of elements.
This is the plot : I want to create a Blog module on my application.
To do this, I created a model Page, which describe a Blog Page. And a model PageElement, which describe a Post on the blog. Each Page can contain many PageElement.
A PageElement can have many types, because I want my users could post like just a short text, or just a video, or just a picture. I also would like (for example) the user could just post a reference to another model (like a reference to an user). Depending of the kind of content the user posted, the HTML page will display each PageElement in a different way.
But I don't know what is the right way to declare the PageElement class in order to support all these cases :(
Here is my Page model :
class Page(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
# Basical informations
title = models.CharField(max_length=150)
description = models.TextField(blank=True)
# Foreign links
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='pages_as_user'
)
created_at = models.DateTimeField(default=timezone.now)
# Other fields ....
class Meta:
indexes = [
models.Index(fields=['uuid']),
models.Index(fields=['user', 'artist'])
]
For now, I have two solutions, the first one use inheritance : When you create a new post on the blog, you create an Element which inherit from PageElement model. Here are my different Models for each cases :
class PageElement(models.Model):
page = models.ForeignKey(
Page,
on_delete=models.CASCADE,
related_name='%(class)s_elements'
)
updated_at = models.DateTimeField(default=timezone.now)
created_at = models.DateTimeField(default=timezone.now)
class PageImageElement(PageElement):
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
class PageVideoElement(PageElement):
video = models.FileField(null=True)
video_url = models.URLField(null=True)
class PageTextElement(PageElement):
text = models.TextField(null=True)
class PageUserElement(PageElement):
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='elements'
)
This solution would be the one I have choosen if I had to work with "pure" Python. Because I could stored each PageElement in a dictionnary and filter them by class. And this solution could be easily extended in the futur with new type of content.
But with Django models. It seems that is not the best solution. Because it will be really difficult to get all PageElement children from the database (I can't just write "page.elements" to get all elements of all types, I need to get all %(class)s_elements elements manually and concatenate them :/). I have thinked about a solution like below (I don't have tried it yet), but it seems overkilled for this problem (and for the database which will have to deal with a large number of request):
class Page(models.Model):
# ...
def get_elements(self):
# Retrieve all PageElements children linked to the current Page
R = []
fields = self._meta.get_fields(include_hidden=True)
for f in fields:
try:
if '_elements' in f.name:
R += getattr(self, f.name)
except TypeError as e:
continue
return R
My second "solution" use an unique class which contains all fields I need. Depending of the kind of PageElement I want to create, I would put type field to the correct value, put the values in the corresponding fields, and put to NULL all other unused fields :
class PageElement(models.Model):
page = models.OneToOneField(
Page,
on_delete=models.CASCADE,
related_name='elements'
)
updated_at = models.DateTimeField(default=timezone.now)
created_at = models.DateTimeField(default=timezone.now)
TYPES_CHOICE = (
('img', 'Image'),
('vid', 'Video'),
('txt', 'Text'),
('usr', 'User'),
)
type = models.CharField(max_length=60, choices=TYPES_CHOICE)
# For type Image
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
# For type Video
video = models.FileField(null=True)
video_url = models.URLField(null=True)
# For type Text
text = models.TextField(null=True)
# For type User
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='elements',
null=True
)
With this solution, I can retrieve all elements in a single request with "page.elements". But it is less extendable than the previous one (I need to modify my entire table structure to add a new field or a new kind of Element).
To be honnest, I have absolutly no idea of which solution is the best. And I am sure other (better) solutions exist, but my poor Oriented-Object skills don't give me the ability to think about them ( :( )...
I want a solution which can be easily modified in the future (if for example, I want to add a new Type "calendar" on the Blog, which reference a DateTime). And which would be easy to use in my application if I want to retrieve all Elements related to a Page...
Thanks for your attention :)
I'm not sure it fits your problem but using GenericForeignKeys/ContentType framework may be appropriate in this case. It's quite powerful when one grasps the concept.
Example construct:
class Page(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
page_element = GenericForeignKey('content_type', 'object_id')
...
You can now connect any model object by the GenericFK to the Page model. So adding a new type (as a new model), at a later stage, is not intrusive.
Update:
As a comment pointed out this construct doesn't support many PageElements in a good way for a Page.
To elaborate, one way to solve that problem, still taking advantage of the GenericFK...
class PageElement(models.Model):
class Meta:
unique_together=('page', 'content_type', 'object_id') # Solve the unique per page
page = models.ForeignKey(Page, related_name='page_elements')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
A Page can have many "abstract" PageElements and content_object is the "concrete PageElement model/implementation".
Easy to retrieve all elements for a specific page and allows inspection of the ContentType to check the type of element etc.
Just one way of many to solve this particular problem.
To establish the relationship between Page and PageElement in Django you would rather use Foreign Key relationship, than inheritance.
class PageImageElement(PageElement):
page = models.ForeignKey(Page,
on_delete=models.CASCADE,
related_name='images')
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
Every user's post would create an instance of Page. Every addition of image to the Page would create an instance of PageImageElement and you could query for them using the related name. This way would be really easy to access all video, image, text modules of a single Page.
On a related note, I would say that PageElement class could be abstract see the docs and if you declare fields as possibly containing null values as in video = models.FileField(null=True) then it might be worth declaring blank=True as well, otherwise there will be errors when creating the object with these fields undefined. Discussed, for example, here: differentiate null=True, blank=True in django
I can't just write "page.elements" to get all elements of all types
Well actually, you can if you use multi-table inheritance. The problem is that all records returned are instances of PageElement, meaning you lose all information of the subclass type and the additional data these child objects may hold.
There are quite a lot of packages that tackle this polymorphism problem:
django packages: Model inheritance
I use django and postgress and have a very odd exception.
I have a model object called ProductModel (with upc as a unique_id).
This is how the model looks like:
class ProductModel(models.Model):
def __str__(self):
return self.clean_name + " " + str(self.product_upc)
product_name = models.CharField(max_length=300, blank=True)
product_upc = models.CharField(max_length=300, primary_key=True)
official_price = models.DecimalField(default=0, decimal_places=5, max_digits=10)
mrsp = models.DecimalField(default=0, decimal_places=5, max_digits=10)
last_seen = models.DateTimeField(default=now)
clean_name = models.CharField(max_length=200, default='')
unfortunately (as I understand now) at the begining of times I made a mistake and created a class called product that inherits the ProductModel - this class has some regular methods nothing fancy.
class Product(ProductModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.create_clean_name()
self.img_list = []
self.scraped_content = ""
self.specs = {}
Now, I was sure that this class has nothing to do with the db and the db doesnt aware of its existence - but now When I try to delete some product records using this command:
p = ProductModel.objects.all().filter(last_seen__month__lt=4,product_name__contains='a')
p.delete()
I get the following error -
django.db.utils.IntegrityError: update or delete on table "my_app_productmodel" violates foreign key constraint "f64b7bfb6c6019a35bf6b81e4125240f" on table "my_app_product"
DETAIL: Key (product_upc)=(852896336240) is still referenced from table "my_app_product"
And in this point I got totally lost and confused what the h*** is table "my_app_product" - why it exists??
I went to my PostgreSQL and found out that this table is really exists.
I'd like to understand how does it work and how I delete records in this situation.
ohh and one more detail - as I try to delete my records via django admin I manage to do it without any problems.
Thanks to the kind helper.
Here is what you actually did: https://docs.djangoproject.com/en/1.11/topics/db/models/#multi-table-inheritance
Subclassing a model (that is not defined as abstract) will create a new table called Product that has a OneToOneField to ProductModel pk which is product_upc. You are trying to delete a ProductModel which has a reference to Product table, this is why you get the error.
Depending on how you use that Product class I suggest to just change its superclass to object and run makemigrations to remove the table, since you don't actually need it.
After looking for a way to check if a model instance can be deleted in django, I found many options, but none was working as expected. Hope this solution can help.
Let start by creating an Abstract model class which can be inherited by other model
class ModelIsDeletable(models.Model):
name = models.CharField(max_length=200, blank=True, null=True, unique=True)
description = models.CharField(max_length=200, blank=True, null=True)
date_modified = models.DateTimeField(auto_now_add=True)
def is_deletable(self):
# get all the related object
for rel in self._meta.get_fields():
try:
# check if there is a relationship with at least one related object
related = rel.related_model.objects.filter(**{rel.field.name: self})
if related.exists():
# if there is return a Tuple of flag = False the related_model object
return False, related
except AttributeError: # an attribute error for field occurs when checking for AutoField
pass # just pass as we dont need to check for AutoField
return True, None
class Meta:
abstract = True
Example
So let say we have three model Organization and Department and StaffType
So many Department can be in an Organization
And an Organization has a particular StaffType
class StaffType(ModelIsDeletable):
pensionable = models.BooleanField(default=False)
class Organization(ModelIsDeletable):
staff_type = models.ForeignKey(to=StaffType)
class Department(ModelIsDeletable):
organization = models.ForeignKey(to=Organization, to_field="id")
so let say after adding some information you want to remove an organization model instance
that is already tied to a Department
For instance we have
Organization Table => (name = Engineering, pk = 1)
Department Table => (name=Developer, organization_fk=1, pk=1)
Now when you try to delete an organization after get it with the pk
a_org = Organization.objects.get(pk=1)
With this at hand you can check if it deletable
deletable, related_obj = a_org.is_deletable()
if not deletable:
# do some stuff with the related_obj list
else:
# call the delete function
a_org.delete()
Your question appears to be "How to detect what related model objects would be deleted if I delete this model object?" or "How to detect what related rows would be deleted if I delete this row?"
Another option is to use a transaction, do the delete, save the information provided by django, but rollback before committing the change. This works in databases like Postgres and MySQL, but I don't know about others.
In this example I want to know what will be deleted if I delete my organization named 'pdemo', and I see that it has 408 related Property objects.
https://gist.github.com/cgthayer/25aa97bb4b74efb75e3467fb7bbdaacb
>>> from django.db import transaction
>>> transaction.set_autocommit(autocommit=False)
>>> o = Organization_v2.objects.get(name='pdemo')
>>> del_info = o.delete()
>>> del_info
(1404, {'data.Property': 408, [..more stuff..], 'data.Organization_v2': 1})
>>> Property.objects.filter(scope__organization_id=o).count()
0
>>> transaction.rollback()
>>> o = Organization_v2.objects.get(name='pdemo')
>>> Property.objects.filter(scope__organization_id=o).count()
408
This could be translated into a generic function.
When looking into this, I found many old solutions that use the functions in the django.contrib.admin to determine this, but that's an undocumented api that seems to change from time to time, so using transactions seems to be easier iff your database supports it.
i have following models
class tags(models.Model):
tag = models.CharField(max_length=15) # Tag name
tagDescription = models.TextField() # Tag Description
tagSlug = models.TextField() # Extra info can be added to the existing tag using this field
class stores(models.Model):
storeName = models.CharField(max_length=15) # Store Name
storeDescription = models.TextField() # Store Description
storeURL = models.URLField() # Store URL
storePopularityNumber = models.IntegerField(max_length=1) # Store Popularity Number
storeImage = models.ImageField(upload_to=storeImageDir) # Store Image
storeSlug = models.TextField() # This is the text you see in the URL
createdAt = models.DateTimeField() # Time at which store is created
updatedAt = models.DateTimeField() # Time at which store is updated
storeTags = models.ManyToManyField(tags)
class tagsAdmin(admin.ModelAdmin):
list_display = ('tag', 'tagDescription', 'tagSlug')
class storesAdmin(admin.ModelAdmin):
list_display = ('storeName','storeDescription','storeURL',
'storePopularityNumber','storeImage',
'storeSlug','createdAt','createdAt','storeTags'
)
admin.site.register(tags,tagsAdmin)
admin.site.register(stores,storesAdmin)
Whenever I am trying to run command : python manage.py syncdb
I got the error: django.core.exceptions.ImproperlyConfigured: 'storesAdmin.list_display[8]', 'storeTags' is a ManyToManyField which is not supported.
I don't understand what I am doing wrong here. I want to simply display all the models in the django admin site.
You can't reference a Many2ManyField like that, you have to use a method instead in the stores class that looks like this
def get_tags():
return self.storeTags.all()
and reference that in your list_display(...'get_tags')
This is done because the M2M field would result in lots of SQL queries that would slow the entire thing down so therefore the choice would have to come from the developer and not from the framework.
Please check:
ModelAdmin.list_display
"ManyToManyField fields aren’t supported, because that would entail executing a separate SQL statement for each row in the table. If you want to do this nonetheless, give your model a custom method, and add that method’s name to list_display. (See below for more on custom methods in list_display.)"
You can use a custom method to show values of ManyToManyField or simply remove storeTags from list_display