Unable to load fixture for through model django - django

What I am trying to do?
I have created a fixture for a through model and now I want to load it in my database.
What is the problem?
While loading the fixture using Django loaddata command for through model I get this error:
django.core.serializers.base.DeserializationError: Problem installing fixture
'm.json': ['“Dave Johnson” value must be an integer.']:(room.membership:pk=None)
field_value was 'Dave Johnson'
models.py
class Person(models.Model):
name = models.CharField(max_length=100, unique=True)
def natural_key(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=100, unique=True)
members = models.ManyToManyField(Person, through='Membership')
def natural_key(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
joined_on = models.DateTimeField(auto_now_add=True)
objects = MembershipManager()
def natural_key(self):
return self.person.name, self.group.name
I am creating fixture like this:
python manage.py dumpdata room.Membership
--natural-foreign --natural-primary > m.json
which creates the following json:
[
{
"model": "room.membership",
"fields": {
"person": "Dave Johnson",
"group": "Django Learning",
"joined_on": "2020-12-03T13:14:28.572Z"
}
}
]
I have also added get_by_natural_key method in the manager for through model like this:
class MembershipManager(models.Manager):
def get_by_natural_key(self, person_name, group_name):
return self.get(person__name=person_name, group__name=group_name)
And loading the fixture
python manage.py loaddata m.json
Other models are working fine. I can load them without any issue it is only the through model which is not working.

You are creating a model, not a manager. You should also ensure that the combination of fields is unique:
class MembershipManager(models.Manager):
def get_by_natural_key(self, person_name, group_name):
return self.get(person__name=person_name, group__name=group_name)
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
joined_on = models.DateTimeField(auto_now_add=True)
objects = MembershipManager()
class Meta:
constraints = [
models.UniqueConstraint(
fields=['person', 'contact'], name='unique_person_group'
)
]
def natural_key(self):
return self.person.name, self.group.name
For the Person and Group models, you will need managers as well:
class NameManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
class Person(models.Model):
name = models.CharField(max_length=100, unique=True)
objects = NameManager()
def natural_key(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=100, unique=True)
members = models.ManyToManyField(Person, through='Membership')
objects = NameManager()
def natural_key(self):
return self.name

After drilling down more on django source code about loaddata command I found out that it uses django deserialization internally. So I thought to read about serialization and deserialization which has this section in the docs that talks about dependencies during serialization.
So based on that following changes fixed the issue:
Solution:
Update the through model natural_key method and add natural_key dependencies:
def natural_key(self):
return self.person.natural_key(), self.group.natural_key()
natural_key.dependencies = ['room.Person', 'room.Group']
I also updated the natural_key of both Person and Group models to return tuple rather than single element i.e return self.name, . Moreover added managers for both the models.

Related

django select related not giving expected result

I am querying select related between two models Requirements and Badge Requirement has a related badge indicated by badge_id Models are,
class Badge(models.Model):
level = models.PositiveIntegerField(blank=False, unique=True)
name = models.CharField(max_length=255, blank=False , unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name = _("Badge")
verbose_name_plural = _("Badges")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("Badge_detail", kwargs={"pk": self.pk})
""" Requirement Model for requirements """
class Requirement(models.Model):
number = models.PositiveIntegerField(blank=False)
badge = models.ForeignKey(Badge, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
class Meta:
verbose_name = _("Requirement")
verbose_name_plural = _("Requirements")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("Requirement_detail", kwargs={"pk": self.pk})
In My view I try to join both tables and retrieve. It is,
""" ajax requirements in requirements table """
def get_requirements(request):
requirements = Requirement.objects.all().select_related('badge').values()
print(requirements)
return JsonResponse(list(requirements), safe=False)
The result is,
to the frontend,
to the backend,
Why does it not give me both tables' values?
Best way to achieve that is using Serializers which are the key component to deal with transforming data from models to JSON and the inverse:
To use this approach you can create the following serializers:
yourapp.serializers.py
from rest_framework.serializers import ModelSerializer
from yourapp.models import Requirement, Badge
class BadgeSerializer(ModelSerializer):
class Meta:
model = Badge
fields = '__all__'
class RequirementSerializer(ModelSerializer):
badge = BadgeSerializer()
class Meta:
model = Requirement
fields = '__all__'
After that you should go to your views.py file and do the following changes:
from yourapp.serializers import RequirementSerializer
def get_requirements(request):
reqs = Requirement.objects.select_related('badge')
return JsonResponse(RequirementSerializer(reqs, many=True), safe=False)
In this way you will have a more flexible way to add or remove fields from the serializer, and your application is also going to be more decoupled and easy to maintain.

How to create or update more than one objects of different models from one end point

How to update more than one object of different model types from one end point. I tried it many ways but i still fails.I tried through nested serializer and create method, but it is still not working
class Student(models.Model):
name = models.CharField(max_length=300)
sex = models.CharField(choices=SEX_CHOICES,max_length=255,
null=True)
Category = models.CharField(max_length=100, null=True)
def __str__(self):
return self.name
class Registration(models.Model):
registration_no = models.CharField(max_length=255,
unique=True)
student = models.OneToOneField(Student,
on_delete= models.CASCADE, related_name='registrations')
def __str__(self):
return self.registration_no
class RegistrationSerializer(serializers.ModelSerializer):
class Meta:
model = Registration
fields = '__all__'
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__'
class StudentDataMigrateSerializer(serializers.Serializer):
student = StudentSerializer()
registation = RegistrationSerializer()
In Django Rest Framework by default the nested serializers are read only. To have a writable nested serializer you need to implement create() and/or update() methods.
Take a look at the official documentation https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
class StudentDataMigrateSerializer(serializers.Serializer):
student = StudentSerializer()
registation = RegistrationSerializer()
def create(self, validated_data):
# save the data

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:

Display full names in Form ChoiceField but saving ID's

I have model Person - from another database
I copied all person_id to custom_id.
models.py
class Employee(models.Model):
custom_id = models.CharField(max_length=20, unique=True)
#property
def person(self):
return Person.objects.get(person_id='%s' % self.custom_id)
def __str__(self):
return '%s' % self.custom_id
class Task(models.Model):
employee = models.ManyToManyField(Employee, blank=True, null=True)
task = models.CharField(max_length=100)
comment = models.CharField(max_length=200)
def __str__(self):
return '%s' % self.task
I add my method person() to Employee which allow me to access other objects model in another database:
So basically when I type this in shell:
Employee.objects.get(custom_id='123').person.full_name
u'Adam Dylan'
I have a ModelForm which use ModelMultipleChoiceField
forms.py
class TaskCreateForm(forms.ModelForm):
employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all())
class Meta:
model = Task
But Employee.objects.all() returns bunch of custom_id's.
What I want is to show in form "Employee(..).person.full_name" but saving only custom_id's.
I am not sure why you think the answer I gave to your other question does not work here. Did you try the following? If it does not work, how exactly does it fail?
class EmployeeMultipleChoiceField(ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.person.full_name
class TaskCreateForm(forms.ModelForm):
employee = EmployeeMultipleChoiceField(queryset=Employee.objects.all())
class Meta:
model = Task

Django 1.5 AbstractBaseUser with char primary key not JSON serializable

I am having the following custom user model trying to use the Django 1.5 AbstractBaseUser:
class Merchant(AbstractBaseUser):
email = models.EmailField()
company_name = models.CharField(max_length=256)
website = models.URLField()
description = models.TextField(blank=True)
api_key = models.CharField(blank=True, max_length=256, primary_key=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['email','website']
class Meta:
verbose_name = _('Merchant')
verbose_name_plural = _('Merchants')
def __unicode__(self):
return self.company_name
The model works perfectly and database is as expected, but the problem is when I try to dumpdata to create fixtures for my tests.
python manage.py dumpdata --natural --exclude=contenttypes --exclude=auth.permission --indent=4 > fixtures/initial_data.json
Then I get the error:
CommandError: Unable to serialize database: <Merchant: Test Shop> is not JSON serializable
Do you have ideas what could be the reason for this. Could it be the charfield primary key or something with the abstractbaseuser model?
Thanks
After some time spend I found the problem. Actually it was not in Merchant model but in Product that has foreign key to Merchant:
class Product(models.Model):
name = models.CharField(max_length=200)
#merchant = models.ForeignKey(Merchant, to_field='api_key')
merchant = models.ForeignKey(Merchant)
url = models.URLField(max_length = 2000)
description = models.TextField(blank=True)
client_product_id = models.CharField(max_length='100')
objects = ProductManager()
class Meta:
verbose_name = 'Product'
verbose_name_plural = 'Products'
unique_together = ('merchant', 'client_product_id',)
def __unicode__(self):
return self.name
def natural_key(self):
return (self.merchant, self.client_product_id)
the natural_key method in the model returned self.merchant instead of self.merchant_id so it was trying to serialize the whole merchant object to create a natural key. After switching the natural_key method to the following one the problem was fixed:
def natural_key(self):
return (self.merchant_id, self.client_product_id)