unique_together and M2M field - django

I have the following Model:
class CodeSynonyms(models.Model):
code = models.ForeignKey(Codes, on_delete=models.CASCADE)
websites = models.ManyToManyField(Websites)
synonym = models.Charfield(max_length=10)
The idea is that Websites use the Synonyms for specific Codes. One Website can't have few Synonyms for a Code; various Websites can share the same Synonym for specific Code. The following won't work:
class Meta:
unique together = ('code', 'websites')
" 'unique_together' refers to a ManyToManyField 'websites', but ManyToManyFields are not permitted in 'unique_together' "
Is there a way to solve this keeping the M2M relation? It would be handy to have it

you can use through in ManyToManyFieldand connect your many to many relations through custom intermediate table. then add unique_together there:
class CodeSynonyms(models.Model):
# add through field
websites = models.ManyToManyField(Websites, through='WebsiteCode')
synonym = models.Charfield(max_length=10)
class WebsiteCode(models.Model):
code_synonym = models.ForeignKey(CodeSynonyms, on_delete=models.CASCADE)
website = models.ForeignKey(Websites, on_delete=models.CASCADE)
code = models.ForeignKey(Codes, on_delete=models.CASCADE)
class Meta:
unique together = ('code', 'website')

Related

Search field on generic foreign key field

I am trying to add a search field to the Django admin model CreditsAdmin that will allow me to search the email of related customer objects. The Customer object has a generic foreign key to many different typed of object all of which have an email.
I've already tried defining the function customer_email on the Customer object and using it as a search field, but this gives the error Related Field got invalid lookup: customer_email
class Customer(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
#property
def customer_email(self):
return str(self.content_object.email)
class Credits(models.Model):
credits_remaining = models.IntegerField()
current_period_start = models.DateTimeField()
current_period_end = models.DateTimeField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
class CreditsAdmin(admin.ModelAdmin):
list_display = (
'current_period_start',
'current_period_end',
'customer_name',
'credits_remaining',
)
search_fields = ('customer__customer_email',)
I'd like to be able to search the emails of related generic objects on the Customer model from the CreditsAdmin interface. In particular, one of the content_types that the customer objects relate to is django's auth.User model, but there are also others.
You cannot use property in search_fields, because it looks for columns in database level.
GenericRelation might be a solution. You can create fields in related(by content type) models. For example:
class CntObject(models.Model):
customers = GenericRelation(Customer, related_query_name='cnt_objects')
And in admin panel for Credits:
class CreditsAdmin(admin.ModelAdmin):
list_display = (
'current_period_start',
'current_period_end',
'customer_name',
'credits_remaining',
)
search_fields = ('customer__cnt_objects__email',)
There is a not best thing with this answer. You must be sure that all related content_objects will have email field. May be issues with performance, didn't test it.
Other solution might be, your custom get_search_results method in admin class.

Django ManyToMany Validation Constraint

I have a ManyToMany link, and a Foreign key which links three objects.
[A]>--<[B]>---[C]
A can belong to many of B, and vice versa. However, A can only belong to B objects with the same parent C.
I'm trying to do something in the clean() method of the model. I'm using Django Rest Framework and no ModelForms or anything like that. I haven't been able to figure it out yet
Simplified Sample Code
class Device(models.Model):
name = models.CharField(max_length=20)
projects = models.ManyToManyField(Project, 'devices')
details = models.CharField(max_length=200)
serial = models.CharField(max_length=20)
address models.GenericIPAddressField(default="0.0.0.0")
port = models.IntegerField(default=3000)
jumpers = models.IntegerField(default=0)
install_date = models.DateField(blank=True, null=True)
class Project(models.Model):
name = models.CharField(max_length=20)
description = models.CharField(max_length=250)
area = models.ForeignKey(Area)
class Area(models.Model):
name = models.CharField(max_length=20)
description = models.CharField(max_length=250)
owner = models.CharField(max_length=20) # microservice doesn't have owner group - field in JWT
Serializers
class AreaSerializer(serializers.ModelSerializer):
class Meta:
model = Area
fields = ('name', 'description', 'owner')
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('id', 'name', 'description', 'area')
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ('id', 'name', 'projects', 'details', 'serial',
'address', 'port', 'jumpers', 'install_date')
I am not sure where and how do you want to validate your data. So I am just posting the method which can validate if a project can be linked to a device or not based on your specific check.
def validate_project(device, project):
projects = device.projects.all()
areas = set(projects.values_list('area', flat=True))
if len(areas) > 1:
raise serializers.ValidationError('projects are not valid')
return areas.pop() == project.area_id
EDIT:
You have to use a intermediate model for storing the relationship between device and project.
class Membership(models.Model):
device = models.ForeignKey(Device, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
area = models.ForeignKey(Area, on_delete=models.CASCADE)
use the above membership model to store the many to many relations.
On your device model use this field to define the many to many relation.
projects = models.ManyToManyField(Project, through='Membership')
checkout the docs
Now when you link a device and project you will have explicitly add the area id as well. Before adding now you can check if the project is valid or not based on the area associated.
(ignore the wonky field types, cba)
What it boils down to is: you need a table BC that stores relations between B and C. Table A would then select only from those relations through the intermediary m2m table ABC (or ditch ABC, couldn't figure out how to draw m2m with the online tool). I think I mixed up B and C in this picture, swap them around depending on whether B or C holds the ForeignKey.
Please correct if I'm wrong!

Include Specific Foreign Keys in DRF Serializer

Here's two simple models to use as an example:
class Author(models.Model):
name = models.CharField(blank=True, max_length=50)
age = models.IntegerField(null=True, )
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
date = models.DateField()
Now what I'd like to do, is create a view for Book that pulls in one of the values from Author using the Django Rest Framework. Here's an example ModelSerializer:
class BookMetaSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title','date','author__name',)
The trouble is that one can't access the fields of a foriegn key in the DRF like I gave above author__name. I haven't been able to figure out how to do this based on the documentation. All help is appreciated, thanks!
You can define author_name field with source argument to get the name of an author.
From the DRF docs on source argument:
The name of the attribute that will be used to populate the field. May
be a method that only takes a self argument, such as
URLField('get_absolute_url'), or may use dotted notation to traverse
attributes, such as EmailField(source='user.email').
Final Code:
class BookMetaSerializer(serializers.ModelSerializer):
# use dotted notation to traverse to 'name' attribute
author_name = serializers.CharField(source='author.name', read_only=True)
class Meta:
model = Book
fields = ('title','date','author_name',)

Unique field in Django Model for each foreign key

I am defining a set of models, which have references to each other. They are a model for a documentation app, which is as follows
class Document(models.Model):
text = models.TextField()
class Chapter(models.Model):
doc = models.ForeignKey('Document')
chapter = models.IntegerField()
I want the integer field to be unique per document, but am not sure how to do so. I know there is a unique parameter for each field, but it seems like it is unique for the entire table, which is not what I want.
You can use unique together in your model is meta:
class Chapter(models.Model):
doc = models.ForeignKey('Document')
chapter = models.IntegerField()
class Meta:
unique_together = (("doc", "chapter"),)
Here's the doc
(Django 3.1) edit: Using unique_together is now discouraged by the docs and may be deprecated in the future, as per the docs. Use UniqueConstraint instead:
class Chapter(models.Model):
doc = models.ForeignKey('Document')
chapter = models.IntegerField()
class Meta:
constraints = [
models.UniqueConstraint(
fields=['doc', 'chapter'],
name='unique chapter'
)
]

Django generic relation problem

I'm having problems coming up with a filter in one of my views. I'm creating a site with blog entries, news articles, and reviews. The entries and articles have generic relations with the reviews, because the reviews can tag either of them. What I'm trying to do is to sort the entries/articles based on the sum of the ratings of reviews newer than a certain date.
Here are the simplified models:
class Entry(models.Model):
name = models.CharField(max_length=50)
reviews = generic.GenericRelation(Review)
class Article(models.Model):
name = models.CharField(max_length=50)
reviews = generic.GenericRelation(Review)
class Review(models.Model):
rating = models.IntegerField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
target = generic.GenericForeignKey('content_type', 'object_id')
timestamp = models.DateTimeField(auto_now_add=True)
So, given that I needed to find a sum, I tried using annotate and aggregate, but I ran into two problems. The first one is that apparently generic relations and annotations don't work nicely together: https://code.djangoproject.com/ticket/10461. The second issue is that I don't think it's possible to only sum part of the reviews (in this case, with timestamp__gte=datetime.now()). Am I doing this the wrong way?
I also thought about doing this the other way around:
Review.filter(timestamp__gte=datetime.now(), target__in=something).aggregate(Sum('rating'))
But since I'm trying to order the reviews based on this, don't I need to start with Review.something so I can use order_by?
Thanks.
I would highly suggest using Multi-table inheritance instead of generic foreign keys:
class ReviewedItem(models.Model):
item_type = models.IntegerField() # exclude me on admin
def save(self):
self.item_type = self.ITEM_TYPE
super(ReviewedItem, self).save()
class Entry(ReviewedItem):
ITEM_TYPE = 1
name = models.CharField(max_length=50)
class Article(ReviewedItem):
ITEM_TYPE = 2
name = models.CharField(max_length=50)
class Review(models.Model):
item = models.ForeignKey(ReviewedItem)
rating = models.IntegerField()
timestamp = models.DateTimeField(auto_now_add=True)
After doing some research, I found out that the only way to solve my problem was to write custom sql using the "extra" method of QuerySets.