Unable to set many to many relationship in overwritten save method - python

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 .

Related

How to auto update a models attribute on creation of another model

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.

How to access data using reverse foreign key reference in django

I have a model named UserProfile and a model PersonalInformation. I would like to fetch all the data of PersonalInformation using UserProfile model when the user is logged into the webiste but i have a foreign key refernce in the PersonalInformation model with the UserProfile model so how do i fetch the personal information using UserProfile model?
User Profile Model :
class UserProfile(models.Model):
"""Represents a user's model inside our system"""
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
profile_picture = models.ImageField(upload_to='photos/%y/%m/%d/')
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
highest_degree_earned = models.CharField(max_length=255, blank=False)
college_name = models.CharField(max_length=255, blank=False)
graduation_year = models.IntegerField(default=2020, blank=False)
Personal Information Model :
class PersonalInformation(models.Model):
"""Represents a user's personal Infromation inside our system"""
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
mobile = models.CharField(max_length=10 ,blank=True)
bio = models.TextField(max_length=200, blank=True)
college_university = models.CharField(max_length=100, blank=False)
course = models.CharField(max_length=100, blank=False)
First of all, in the code, you are showing you have the names of the models wrong. The UserProfile model name is set as PersonalInformation, change it or the migrations won't work (it's not accepted on the database no matter which one you're using).
Referent to the question you're asking, to fetch the related instance of PersonalInformation of a certain UserProfile instance you should just query the next:
user = UserProfile.objects.get(id='') #Introduce the id of the user you want to fetch its personal information.
user.personalinformation_set.all() # This will return you a QuerySet with all the related instances of PersonalInformation class.
user.personalinformation_set.get(id='') #To get a specific one or you may use a filter to get a filtered QS.
If you want, you can use the related_name attribute for ForeignKey class in order to set a different name from personalinformation_set.
I recommend you too to read the Django documentation, it's really well explained and clear I think:
https://docs.djangoproject.com/en/2.2/topics/db/examples/many_to_one/
As I've seen in a comment, you may also think to use a OneToOne relation instead of ForeignKey if you only expect one instance of PersonalInformation per User. The documentation is at:
https://docs.djangoproject.com/en/2.2/topics/db/examples/one_to_one/

How to filter a one-to-one generic relation with Django?

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

Passing additional attributes all the way thru to nested serializers

I've been having issues passing additional attributes thru using Django Rest Framework with nested serializers.
I've created a Document model that has a ForeignKey owner/creator relationship, and several other ForeignKey related models. Some of those other model have an owner/creator ForeignKey associated as well.
class Document(models.Model):
owner = models.ForeignKey('auth.User',related_name='document')
candidate = models.ForeignKey(
Candidate,
on_delete=models.CASCADE,
blank=True,
null=True,
)
class Candidate(models.Model):
owner = models.ForeignKey('auth.User', related_name='candidates')
first_name = models.CharField(max_length=30, blank=True, null=True)
When saving down the Document model with a nested serializer and a custom create() method, I can pass all fields down, however, the nested models don't seem to be able to pick up the Owner field, regardless of how I pass it in. Creating a Candidate alone is fine.
class CandidateSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Candidate
fields = (
'pk',
'first_name',
'owner',
)
class DocumentSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
candidate = CandidateSerializer(required=True)
class Meta:
model = Document
fields = (
'owner',
'candidate',
)
def create(self, validated_data):
candidate_data = validated_data.pop('candidate')
document = Document.objects.create(**validated_data)
Candidate.objects.create(**candidate_data)
With DocumentSerializer set up like this, I get errors like this while trying to do a POST to Document with nested fields.
IntegrityError: NOT NULL constraint failed: dossier_candidate.owner_id
When I modify the DocumentSerializer.create() method to try to pick up the owner, it seems that owner = serializers.ReadOnlyField(source='owner.username') is now out of scope, even though it should be under the class.
i.e.,
When I try to create the Candidate object with
Candidate.objects.create(owner, **candidate_data)
I get this error :
NameError at /rest/documents/
global name 'owner' is not defined
When I try this
Candidate.objects.create(self.owner, **candidate_data)
I get this error:
AttributeError: 'DocumentSerializer' object has no attribute 'owner'
What's the proper method of making sure the nested Candidate object is able to be created successfully, picking up the owner field?
First thing first, you won't have the owner to create/update since it's read only.
If you want to get the current user for that, use CurrentUserDefault. It'll be added to the validated_data

Creating many to many relation with AUTH_USER_MODEL in django via intermediary model

I am trying to create the following models. There is a ManyToMany relation from Entry to AUTH_USER_MODEL via the EntryLike intermediate model.
class BaseType(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
creation_time = models.DateTimeField(auto_now_add=True)
last_update_time = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Title(BaseType):
text = models.CharField(max_length=100)
description = models.TextField()
class EntryLike(BaseType):
entry = models.ForeignKey(Entry)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(settings.AUTH_USER_MODEL)
liked_by_users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='EntryLike', through_fields=('entry', 'user'))
Running migrations on the above model scheme throws the error: AttributeError:'str' object has no attribute 'meta'.
Any help in resolving this error would be highly appreciated. Am new to Django & Python, but not to Web Development.
The issue is that settings.AUTH_USER_MODEL is almost certainly not a model instance. It's probably a string that constrains the choices another model can make - settings would be a strange place to leave a model definition.
To do a MTM between the user model and your field above you need need to do:
from django.contrib.auth.models import User
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(User)
def __str__(self):
return self.title
I've added the str function so that it gives a more sensible return when you're manipulating it in admin/shell.
I'd also question whether you need the second set of fields (removed here), as you can use select related between the Entry and EntryLike join table, without any duplication of the fields - you can probably go that way, it's just a bit unnecessary.
Lastly, I'd note that the way I'm using it above just uses the default User object that comes with Django - you may wish to customise it. or extend the base class as you've done here with your own models' base class.
(All of this is predicated on AUTH_USER_MODEL not being a model instance - if it is, can you post the model definition from settings.py? )

Categories

Resources