I have been learning django and django rest from several different courses and have started my own project which will be a forum type web application.
I 4 model classes Category, SubCategory, Thread, and Post.
What I want to be able to do is have an attribute for subcategory called num_threads that can be updated when ever a thread is made for the subcategory it is related to. Similarly I will want a thread to have attributes num_posts and num_views. I have been using foreign keys to relate the models. Now I am not sure that I am doing that part correctly.
Here is my model.py:
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""
Category Class:
Main Categories for GRDG Forum
Creation and Deletion will be restricted to site admin
"""
# Attributes
name = models.CharField(max_length=32, unique=True, blank=False)
description = models.TextField(max_length=150, blank=True)
def __str__(self):
return self.name
class SubCategory(models.Model):
"""
SubCategory Class:
SubCategories will be one to many relation with category ie: Multiple subcategories related to one category
Creation and Deletion will be restricted to site admin
GET methods restriction will vary by subcategory
"""
# References
category = models.ForeignKey(Category, on_delete=models.CASCADE)
# Attributes
name = models.CharField(max_length=32, unique=True, blank=False)
description = models.TextField(max_length=150)
num_threads = models.IntegerField(max_length=None, default=0)
def __str__(self):
return self.name
class Thread(models.Model):
"""
Class Thread
Threads will be one to many relation to subcategory ie: Multiple threads related to one subcategory
Creation and Deletion restricted to authorized and authenticated users
GET methods restriction will vary by parent subcategory.
"""
# References
subcategory = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
user = models.ForeignKey(User, models.CASCADE)
# Attributes
title = models.CharField(max_length=32, unique=False, blank=False)
num_views = models.IntegerField(max_length=None, default=0)
num_posts = models.IntegerField(max_length=None, default=0)
locked = models.BooleanField(default=False)
author = models.CharField(max_length=32, blank=False)
def __str__(self):
return self.title
class Post(models.Model):
"""
Class Posts
Posts will be one to many relation to Thread ie: Multiple posts related to one Thread
Creation and Deletion restricted to authorized and authenticated users
"""
# References
thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
user = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
# Attributes
body = models.TextField(max_length=500, blank=False)
author = models.CharField(max_length=32, blank=False)
I have references in the model classes that assign the foreign key.
Is there a way to automatically update the a models attribute when ever another object with the foreign key is created.
IE: Increased a subcategories num_thread, when a thread with the same foreign key is created.
Looking at other question here I have seen mentions of using custom save methods and after looking at its documentation I am still not sure how it would work.
Please let me know thoughts are other resources I could use.
Please don't store the number of Threads (or any other aggregate) in a model. This is a form of data duplication which is often an antipattern: it means that you will need to implement handlers when Threads are created, updated (refer to another SubCategory) or removed. It turns out that keeping these in sync is often a hard problem, even if the two tables are stored in the same database.
You can remove the num_thread field and use .annotate(…) [Django-doc] and calculate the number of related Threads when necessary with:
from django.db.models import Count
SubCategory.objects.annotate(
num_threads=Count('thread')
)
The SubCategorys that originate from this queryset will then have an extra attribute .num_threads that contains the number of related Thread objects.
Related
After reading all the docs and answers I can find, and burning a whole day, I still can't make this work. Using Django Tables2, I want to show a list of instruments; the instruments table includes a foreign key to an instrumentsType table. When I list the instruments and their attributes, I want to use the foreign key to substitute the textual instrument type description from the other table. I have tried every combination of double underscores and other accessor techniques, but so far all I get is the dreaded -- in the column. (Displaying just the record ID works).
from .models import Instrument
from django_tables2 import A
from instrumenttypes.models import InstrumentType
class InstrumentTable(tables.Table):
id = tables.LinkColumn('instrument_details', args=[A('station_id')])
class Meta:
model = Instrument
template_name = "django_tables2/bootstrap.html"
fields = ("id", "instrument", "nickname", "serialNo",
"instrument__instrumenttype_id__instrumenttypes__id_instrumentType" )
The models involved are:
Instruments model.py
from django.db import models
from instrumenttypes.models import InstrumentType
from stations.models import Station
# Create your models here.
class Instrument(models.Model):
instrument = models.CharField(max_length=40)
instrumenttype = models.ForeignKey(InstrumentType, on_delete=models.CASCADE, null=True)
station = models.ForeignKey(Station, on_delete=models.CASCADE, default=1)
serialNo = models.CharField(max_length=60, null=True, blank=True)
dateAdded = models.DateTimeField("Date Added", null=True, blank=True)
dateRemoved = models.DateTimeField("Date Removed", null=True, blank=True)
status = models.CharField(max_length=10, null=True, blank=True)
nickname = models.CharField(max_length=40, null=True, blank=True)
InstrumentTypes model.py
from django.db import models
class InstrumentType(models.Model):
instrumentType = models.CharField(max_length=40)
Resulting output:
ID Instrument Nickname SerialNo Instrumenttype
4 instr2 nock2 123 —
The most relevant online references I have found are here and here; but having tried the suggestions, no luck. What am I missing?
I've been struggling to get something working too (but I finally did), and I found the examples too brief.
I think you want to get rid of this stuff in the Meta class
"instrument__instrumenttype_id__instrumenttypes__id_instrumentType"
I think Meta.fields should just be a list of field names, and that you refer to the attribute in the other table from the point of view of the type of object you will later pass in to the IntrumentTable constructor (and that is named in the Meta.model attribute:
from django_tables2.utils import Accessor
class InstrumentTable(tables.Table):
instrument_type = tables.Column(accessor=Accessor('instrumenttype.name'))
class Meta:
model = Instrument
template_name = "django_tables2/bootstrap.html"
fields = ("id", "instrument", "nickname", "serialNo", "insrument_type")
Then, in view, make an instance of InstrumentTable
def myview(request):
table_to_render = InstrumentTable(Instrument.objects)
return render(request, sometemplate, {table: table_to_render})
You didn't show your view, and I know there may be a different way. If you have the whole thing in a repo somewhere, leave a link.
two models:
class Branch(models.Model):
name = models.CharField(max_length=50)
square = models.CharField(max_length=50)
branch_id = models.CharField(max_length=5, blank=True)
class Worker(models.Model):
w_branch = models.ForeignKey(Branch, on_delete=models.SET_NULL, null=True, blank=True)
Form
class PayForm(forms.Form):
branch = forms.ModelChoiceField(label='branch', queryset=Branch.objects.all())
worker = forms.ModelChoiceField(label='customer', queryset=Worker.objects.filter())
I dont know how to get queryset of workers based on branch choise whithin the form. And im not sure that it is possible...can you help?
for instance I have 3 branches, and each one of them has 3 workers. If i write queryset=Worker.objects.all(), I get all workers in all branches. But i`m trying to get workers based on user's choise of branch. For instance, If user chooses first branch he can choose in field "worker" only workers from first branch
There is a good package for that already: Django Smart Selects.
https://github.com/jazzband/django-smart-selects
On the documentation it is called chained selects.
https://django-smart-selects.readthedocs.io/en/latest/usage.html#chained-selects
I am trying to create a system where I set up an issue and it automatically creates custom fields that a user will have defined stored in another model. I set my current model up with a many to many relationship to the custom field model and overwrite the save method so that each of the custom defined fields will be added with a default value.
When I use the .add method after saving my issues model, nothing seems to happen, the many to many relationships are not created. The relationships are able to be made within the Django Admin interface.
class Issue(models.Model):
class Meta:
verbose_name = "Issues"
verbose_name_plural = "Issues"
title = models.TextField(null=False, blank=False)
description = models.TextField()
owner = models.ForeignKey(Organisation, on_delete=models.CASCADE)
category = models.ForeignKey(IssueCategory, on_delete=models.CASCADE)
state = models.ForeignKey(IssueStates, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
assignedTo = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
customFields = models.ManyToManyField(IssueCustomFields, blank=True)
def save(self, *args, **kwargs):
super(Issue, self).save(*args, **kwargs)
for x in IssueCustomFieldDefinitions.objects.filter(owner=self.owner):
issueCustom = IssueCustomFields.objects.create(
value=x.default,
fieldDefinition = x,
owner = self.owner,
)
self.customFields.add(issueCustom)
print(self.customFields.all())
I expect that when the Issue model is saved, it iterates through all the custom fields that th user has set up and creates an instance of it as well as establishing relationships. The relationship is never established (the instances are created though)
many to many relation is not depended on save method . if you assign relation between two model with many to many you don't need to save any of those model .
I have a moderation model :
class ItemModeration(models.Model):
class Meta:
indexes = [
models.Index(fields=['object_id', 'content_type']),
]
unique_together = ('content_type', 'object_id')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
published = models.BooleanField(default=False)
...
A descriptor to attach a moderation object on-the-fly :
class ItemModerationDescriptor(object):
def __init__(self, **default_kwargs):
self.default_kwargs = default_kwargs
def __get__(self, instance, owner):
ctype = ContentType.objects.get_for_model(instance.__class__)
try:
moderation = ItemModeration.objects.get(content_type__pk=ctype.id,
object_id=instance.pk)
except ItemModeration.DoesNotExist:
moderation = ItemModeration(item=instance,**self.default_kwargs)
moderation.save()
return moderation
And a model I want to moderate :
class Product(models.Model):
user = models.ForeignKey(
User,
null=True,
on_delete=models.SET_NULL)
created = models.DateTimeField(
auto_now_add=True,
blank=True, null=True,
)
modified = models.DateTimeField(
auto_now=True,
blank=True, null=True,
)
name = models.CharField(
max_length=PRODUCT_NAME_MAX_LENGTH,
blank=True, null=True,
)
moderation = ItemModerationDescriptor()
Now I can see a product 'published' state easily :
p=Product(name='my super product')
p.save()
print(p.moderation.published)
-> False
The generic relation is useful because I will be able to search the objects to moderate whatever the type is : it could be products, images, comments.
to_moderate_qs = ItemModeration.objects.filter(published=False)
Now, how can I get a filtered list of published products ?
I would like to do something like this
published_products_qs = Product.objects.filter(moderation__published=True, name__icontains='sony')
But, of course, it won't work as moderation attribute is not a Django model field.
How can I do that efficiently ? I am thinking a about an appropriate JOIN, but I cannot see how to do that with django without using raw SQL.
Django has a great built in answer for this: the GenericRelation. Instead of your descriptor, just define a generic relation on your Product model and use it as a normal related field:
from django.contrib.contenttypes.fields import GenericRelation
class Product(models.Model):
...
moderation = GenericRelation(ItemModeration)
Then handle creation as you normally would with a related model, and filtering should work exactly as you stipulated. To work as your current system, you'd have to put in a hook or save method to create the related ItemModeration object when creating a new Product, but that's no different from other related django models. If you really want to keep the descriptor class, you can obviously make use of a secondary model field for the GenericRelation.
You can also add related_query_name to allow filtering the ItemModeration objects based only on the Product content type.
WARNING if you do use a GenericRelation note that it has a fixed cascading delete behavior. So if you don't want ItemModeration object to be deleted when you delete the Product, be careful to add a pre_delete hook or equivalent!
Update
I unintentionally ignored the OneToOne aspect of the question because the GenericForeignKey is a one-to-many relation, but similar functionality can be effected via smart use of QuerySets. It's true, you don't have access to product.moderation as a single object. But, for example, the following query iterates over a filtered list of products and extracts their name, the user's username, and the published date of the related ModerationItem:
Product.objects.filter(...).values_list(
'name', 'user__username', 'moderation__published'
)
You'll have to use the content_type to query the table by specific model type.
like this:
product_type = ContentType.objects.get_for_model(Product)
unpublished_products = ItemModeration.objects.filter(content_type__pk=product_type.id, published=False)
For more details on the topic check contenttypes doc
I am a novice in Django and I'm learning the ropes of the admin interface. I have a model with several foreign keys. These foreign keys then reference other foreign keys. On the admin website after I register the Property model and then try to add it I am given a dropdown box for each foreign key model. However this dropdown box only lists existing foreign keys. (http://i.stack.imgur.com/e5LCu.png)
What would be great is if instead of a dropdown box there were extra fields so I could add the foreign key models as I add the property model. That way I wouldn't have to manually add foreign keys and then go back and add some more, and then go back and finally add the property data.
How can I do this? This feels like a simple enough question but after intense Googling I still can't find the answer, so I apologize in advance.
Example of two of my models:
class Address(models.Model):
state = models.ForeignKey('State')
address1 = models.CharField(max_length=200)
address2 = models.CharField(max_length=200)
city = models.CharField(max_length=200)
postal_code = models.CharField(max_length=200)
class Property(models.Model):
address = models.ForeignKey('Address', blank=True, null=True)
borrower = models.ForeignKey('Person', blank=True, null=True)
company = models.ForeignKey('Company', blank=True, null=True)
contract = models.ForeignKey('Contract', blank=True, null=True)
loan_balance = models.IntegerField()
name = models.CharField(max_length=200)
primary_email = models.CharField(max_length=200)
primary_phone = models.CharField(max_length=200)
property_no = models.IntegerField()
Example of my admin.py:
# Register your models here.
class PropertyAdmin(admin.StackedInline):
model = Property
class PersonAdmin(admin.StackedInline):
model = Person
class CompanyAdmin(admin.StackedInline):
model = Company
class ContractAdmin(admin.StackedInline):
model = Contract
class CompletePropertyAdmin(admin.ModelAdmin):
inlines = [PropertyAdmin, PersonAdmin, CompanyAdmin, ContractAdmin]
admin.site.register(Property)
One solution to the problem can be, to create a custom form with fields from both the models and at the time of saving the values, first create the instance of Address model and then with that instance save your final Property model.