Django model primary key as a pair - django

I am trying to make an app where users will login to their profile and can add songs to their favorite list. I am defining a M2M relationship for this.
My question is how to say combination of (song, singer) is unique?
I searched and found that it may be possible through unique_together. Is this the correct way of setting this?
models.py:
from django.contrib.auth.models import User
class Singer(models.Model):
name = models.CharField(max_length=500, unique=True)
def __str__(self):
return self.name
class Song(models.Model):
id = models.AutoField(primary_key=False)
singer = models.ForeignKey(Singer, on_delete=models.CASCADE, related_name='song')
name = models.CharField(max_length=500)
Class Meta:
unique_together = (singer, id)
def __str__(self):
return self.name
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
songs = models.ManyToManyField(Song, related_name='profile')
def __str__(self):
return self.user.username
Please feel free to correct my models.py, if you think the relationship is not correct.
Thanks,

I would use a default primary key (auto field), and use the meta class property, unique_together
class Song(models.Model):
singer = models.ForeignKey(Singer, on_delete=models.CASCADE, related_name='song')
name = models.CharField(max_length=500)
class Meta:
unique_together = (("singer", "name"),)
It would act as a "surrogate" primary key column.

You don't specify id in your model song. I would also recommend to use slug field of django and specify unique on the same. Just in case you have two singers with the same name. Then the second or more you put like abc, abc-1, abc-2, in which case you don't have to change the name and unique_together clause works just fine.
class Song(models.Model):
singer = models.ForeignKey(Singer, on_delete=models.CASCADE, related_name='song')
name = models.CharField(max_length=500)
class Meta:
unique_together = (("singer", "name"),)
def __str__(self):
return self.name

Related

How to display many fields' values with ForeignKey relationship?

Looking for solution of this problem I encountered some similar threads, but referring to older versions of Django/DRF and thus not working in my case.
There are these two models:
class CsdModel(models.Model):
model_id = models.CharField("Item ID", max_length=8, primary_key=True)
name = models.CharField("Item Name", max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class CsdListing(models.Model):
model_id = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='m_id')
name = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='m_name')
(...)
EDIT: Serializers are defined this way:
class CsdModelSerializer(serializers.ModelSerializer):
model_id = serializers.RegexField(regex='^\w{2}\d{3}$', allow_blank=False)
name = serializers.CharField(min_length=6, max_length=50, allow_blank=False)
class Meta:
model = CsdModel
fields = '__all__'
class CsdListingSerializer(serializers.ModelSerializer):
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)
def validate_session_id(self, value):
(...)
class Meta:
model = CsdListing
fields = '__all__'
What I'd like to see, is model_id and name from CsdModel displayed inside a form created based on CsdListing model. But instead, the ID is duplicated:
How should I rebuild the model(s) to have both ID and name displayed in the form?
You should have only one foreign key. But the listing serializer should then reference the model as a nested serializer.
class CsdListing(models.Model):
model = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='listing')
class CsdListingSerializer(serializers.ModelSerializer):
model = CsdModelSerializer()
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)

ForeignKey(unique=True) is usually better served by a OneToOneField

class Categories(models.Model):
id = models.ForeignKey('auth.User',primary_key=True)
name = models.CharField(max_length=100)
description = models.CharField(max_length=300)
def __str__(self):
return Categories.name
class Meta:
order_with_respect_to = 'id'
class Specializations(models.Model):
id = models.ForeignKey('auth.User',primary_key=True)
name = models.CharField(max_length=100)
description = models.CharField(max_length=300)
categories = models.ForeignKey(Categories, on_delete=models.CASCADE)
def __str__(self):
return Specializations.name
courses.Categories.id: (fields.W342) Setting unique=True on a ForeignKey has the same effect as using a OneTo
OneField.
HINT: ForeignKey(unique=True) is usually better served by a OneToOneField.
courses.Courses.id: (fields.W342) Setting unique=True on a ForeignKey has the same effect as using a OneToOne
Field.
HINT: ForeignKey(unique=True) is usually better served by a OneToOneField.
courses.Specializations.id: (fields.W342) Setting unique=True on a ForeignKey has the same effect as using a
OneToOneField.
This warning or error raises although the relation is one to many !! not one to one
The way you designed your models it is essentially a OneToOne relationship, because you set the ForeignKey to be the primary_key (this automatically aplies unique=True).
Do you really want to the user to be the primary_key of Categories or Specializations?
I think you want something like this:
class Categories(models.Model):
user = models.ForeignKey('auth.User')
name = models.CharField(max_length=100)
description = models.CharField(max_length=300)
def __str__(self):
return self.name
class Specializations(models.Model):
user = models.ForeignKey('auth.User')
name = models.CharField(max_length=100)
description = models.CharField(max_length=300)
categories = models.ForeignKey(Categories, on_delete=models.CASCADE)
def __str__(self):
return self.name
With this a User can have many Categories and Specializations
I also changed the __str__ method to self.name

ManyToManyField Serializer throws "This field must be unique" error

I am trying to create a Many-To-Many relationship between two models- Author and Book. My use-case is that I should be able to add a new book to the database with an author that already exists in the database.
models.py
class Author(models.Model):
author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
class Book(models.Model):
title = models.CharField(max_length=50, primary_key=True)
authors = models.ManyToManyField(Author)
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('author_id', 'name')
class BookSerializer(serializers.ModelSerializer):
authors = AuthorSerializer(many=True)
class Meta:
model = Book
fields = ('title', 'authors')
def create(self, validated_data):
book = Book.objects.create(name=validated_data['title'])
for item in validated_data['authors']:
author = Author.objects.get(author_id=item['author_id'])
book.authors.add(author)
return book
Let's say my Author table already has an Author:
1, George RR Martin
Now if I want to add a new book with an existing author, this is the request I send using httpie:
http -j POST http://localhost/books title="The Winds of Winter" authors:='[{"author_id":"1"}]'
and when I do, I get this error:
Output Error
{
"authors": [
{
"author_id": [
"This field must be unique."
]
}
]
}
It seems like the AuthorSerializer is being called which checks the provided author_id against the ones in the database already and throws this error.
Any help on this would be appreciated.
Is there a specific reason you have to use a custom PK field?
Django automatically creates primary key fields for you. If you simply delete that field from your model and your serializer (and create/run a migration on your database), you won't have to specify the pk in your POST call from your frontend, and Django will create an AutoField that auto-increments your model's id:
class Author(models.Model):
# Remove this line and run makemigrations.
# author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
If not, consider using an models.AutoField rather than models.CharField for your primary key field, and again, don't include this in your POST call.
Note, that if you already have a big database created, you might have to do some intricate work in your migration, a la this answer:

Correct way of getting count on FK

What is the correct way of getting the 'contact' count from within my 'Group'?
I was thinking of just creating a new method within 'group' and filter(), but this means hitting the db again which seems bad, right?
class GroupManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(user=user,)
class Group(models.Model):
name = models.CharField(max_length=60)
modified = models.DateTimeField(null=True, auto_now=True,)
#FK
user = models.ForeignKey(User, related_name="user")
objects = GroupManager()
def get_absolute_url(self):
return reverse('contacts.views.group', args=[str(self.id)])
class Contact(models.Model):
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
#FK
group = models.ForeignKey(Group)
group_object.contact_set.count() should do it. Django creates the relation by adding _set to the end of the foreign key's model name.
Have a look at the docs on related objects for more info.

Verbose name for admin model Class in django

Is it possible to create a verbose name for the actual Class model?
class User(models.Model):
fname = models.CharField(max_length=50, verbose_name = 'first name')
So in the admin panel it will be referenced by its verbose name and not 'user' ?
class User(models.Model):
fname = models.CharField(max_length=50, verbose_name = 'first name')
class Meta:
verbose_name = "users"
Source: https://docs.djangoproject.com/en/2.1/topics/db/models/#meta-options
verbose_name and verbose_name_plural both the properties of Meta class are very important to modify the default behaviour of Django to display the name of our models on Admin interface.
You can change the display of your model names using on Admin Interface using verbose_name and verbose_name_plural properties and model fields names using keyword argument verbose_name.
Please find the below 2 examples.
Country model:
class Country(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, help_text="Your country", verbose_name="name")
userid = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"Country {str(self.id)} - {self.name}"
class Meta:
verbose_name = "Country"
verbose_name_plural = "Countries"
If you will not specify verbose_name_plural then Django will take it as Countrys which does not look good as we want it as Countries.
This better fits in the following type of Model.
Gender model:
class Gender(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, help_text="Gender", verbose_name = "name")
userid = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"Gender {str(self.id)} - {self.name}"
class Meta:
verbose_name = "Gender"
You could add a "verbose_name_plural" to the "Meta" class too.
To alter admin model without polluting the model itself, you can utilize a proxy admin model, like this:
# admin.py
from . import models
class Users(models.User):
class Meta:
proxy = True
class UsersAdmin(admin.ModelAdmin):
...
admin.site.register(Users, UsersAdmin)
ConfigAbility._meta.verbose_name = 'config ability'
ConfigAbility._meta.verbose_name_plural = 'config ability'
I did explore this, but don't know whether it's the thing you need. I put those codes in class ConfigAbilityAdmin in Admin.py. Then, the result:
enter image description here
With this approach, you don't need to config Meta method in model class, especially when model class's code is generated from inspectdb...