DRF testing - create object with a many-to-many relation - python

I have the following model Project:
class Project(models.Model):
name = models.CharField(max_length=128)
slug = models.SlugField(blank=True)
assigned_to = models.ManyToManyField(
User, blank=True, related_name="assignees")
created_date = models.DateField(auto_now_add=True)
updated_date = models.DateField(auto_now_add=False, auto_now=True)
If I need to create a new project, all I need to do is supply the name alone. This works for both the Admin dashboard and DRF APIVIEW. But when I try to test the functionality with DRF with an API call, I get the error: [b'{"assigned_to":["This field is required."]}'] Although the field is not required.
My test code below
import datetime
from marshmallow import pprint
from rest_framework.test import APITestCase, APIClient
from freezegun import freeze_time
from accounts.models import User
from .models import Project
#freeze_time("2021-11-14")
class ProjectTests(APITestCase):
client = APIClient()
project = None
name = 'IOT on Blockchain'
dead_line = datetime.date(2021, 11, 21)
data = {
'name': name,
'dead_line': dead_line,
}
def create_user(self):
username = 'test_user1'
email = 'test.user1#gmail.com'
password = '#1234xyz#'
user_type = 'regular'
data = {'username': username,
'email': email,
'password': password,
'user_type': user_type,
}
return User.objects.create_user(**data)
def create_project(self):
project = Project.objects.create(**self.data)
user = self.create_user()
project.assigned_to.add(user)
return project
def test_create_project_without_api(self):
"""
Ensure we can create a new project object.
"""
self.project = self.create_project()
self.assertEqual(Project.objects.count(), 1)
self.assertEqual(self.project.name, 'IOT on Blockchain')
self.assertEqual(self.project.dead_line,
datetime.date(2021, 11, 21))
self.assertFalse(self.project.reached_deadline)
self.assertEqual(self.project.days_to_deadline, 7)
# runs successfully
def test_create_project_with_api(self):
"""
Ensure we can create a new project object with an
API call.
"""
url = 'http://127.0.0.1:8000/api/projects'
project = self.client.post(url, self.data, format='json')
# project.data.assigned_to.set(self.create_user())
pprint(project.__dict__)
self.assertEqual(Project.objects.count(), 1)
self.assertEqual(self.project.name, 'IOT on Blockchain')
self.assertEqual(self.project.slug, 'iot-on-blockchain')
# does not run successfully (error mentioned in text body)
def test_delete_project(self):
"""
We can delete a user
"""
self.project = self.create_project()
self.project.delete()
self.assertEqual(Project.objects.count(), 0)
Edit: Added serializer code
class ProjectWriteSerializer(serializers.ModelSerializer):
"""
This serializer is used for CREATE, UPDATE operations on the Project model.
"""
# We receive list of user ids (ids[int] <= 0) by which we assign
# users to a project
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), many=True)
class Meta:
model = Project
fields = ('id', 'name', 'slug', 'assigned_to')
Any insights and help is very appreciated.

You have specified your own serializer field. As a result, it will no longer look at the blank=True part, and by default serializer fields are required. You can make these optional with:
class ProjectWriteSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(),
many=True,
required=True
)
class Meta:
model = Project
fields = ('id', 'name', 'slug', 'assigned_to')

Related

Django: ValueError: Cannot assign "<User: x>": "UserRelatinoship.x" must be a "User" instance

I am trying to automatically create a relationship between two test users in a migration in Django. My migration code looks like this:
# Generated manually
from django.db import migrations, transaction
from django.contrib.auth import get_user_model
def setup_test(apps, schema_editor):
User = get_user_model()
User.objects.create_superuser("admin", "admin#gm.org", "123")
x = User.objects.create_user(
username="x", email="a#gm.org", password="123"
)
y = User.objects.create_user(
username="y", email="b#gm.org", password="123"
)
UserRelatinoship = apps.get_model("myapp", "UserRelatinoship")
UserRelatinoship.objects.create(x=x, y=y, active=True)
class Migration(migrations.Migration):
dependencies = [
("myapp", "0001_initial"),
("myapp", "0002_manual"),
]
operations = [
migrations.RunPython(setup_test),
]
Model Code:
from django.contrib.auth import get_user_model
User = get_user_model()
class UserRelationship(models.Model):
class Meta:
unique_together = (("x", "y"),)
x = models.ForeignKey(User, related_name="x", on_delete=models.CASCADE)
y = models.ForeignKey(User, related_name="y", on_delete=models.CASCADE)
active = models.BooleanField(default=True)
cancelled = models.BooleanField(default=False)
approved = models.BooleanField(default=False)
denied = models.BooleanField(default=False)
finalized = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self.approved:
self.active = True
if self.cancelled:
self.active = False
super().save(*args, **kwargs)
def __str__(self):
return f"{self.x}:{self.y}"
The users are created fine, but then when I try to create the relationship I get the error ValueError: Cannot assign "<User: x>": "UserRelatinoship.x" must be a "User" instance.. Also, I can create the relationship while running the server and filling out forms manually, even though the code for that looks about identical (except it uses request.user for one part). Note I modified some names from my original code.
You use the function setup_test inside a migration. In a migration directly importing models will naturally not work because the current model state might be different from what the specific migration expects (See notes about Historical models in the documentation). You have used the get_user_model function to get the user model and are using that in your function, this will simply use some functions to import the user model that you have set, hence you get the error.
Hence to fix this you need to use apps.get_model to get the user model:
def setup_test(apps, schema_editor):
User = apps.get_model('auth', 'User')
# In case of a custom user model replace above line like below comment
# User = apps.get_model('some_app', 'CustomUserModel')
User.objects.create_superuser("admin", "admin#gm.org", "123")
x = User.objects.create_user(
username="x", email="a#gm.org", password="123"
)
y = User.objects.create_user(
username="y", email="b#gm.org", password="123"
)
UserRelatinoship = apps.get_model("myapp", "UserRelatinoship")
UserRelatinoship.objects.create(x=x, y=y, active=True)

Fill Many to Many field through API

I am trying to implement an API to add Agent. The Agent has Many To Many field, Role.
I am using Django and Django Rest Framework.
Here is the models :
class Role(models.Model):
code = models.CharField(max_length=50, primary_key=True)
labe = models.CharField(max_length=50)
def __str__(self):
return '%s %s' % (self.labe, self.code)
class Agent(models.Model):
firstName = models.CharField(max_length=60)
lastName = models.CharField(max_length=60)
role = models.ManyToManyField(Role)
So I created Serializers :
class RegistrationSerializer(serializers.ModelSerializer):
role = serializers.PrimaryKeyRelatedField(
many=True, read_only=True)
class Meta:
model = Agent
fields = ['email', 'firstName', 'lastName', 'role',
'phoneNumber', 'experienceWorkYeares'],
def save(self):
agent = Agent.objects.create(
email=self.validated_data['email'],
firstName=self.validated_data['firstName'],
lastName=self.validated_data['lastName'],
phoneNumber=self.validated_data['phoneNumber'],
experienceWorkYeares=self.validated_data['experienceWorkYeares']
role=self.validated_data['role'] // One of my multiple try but doesn`t work.
)
agent.save()
return agent
How can I retrieve the role I sent via Postman and put it in the agent ? for the role I am POSTing role = "CODE1".
Thank you so much in advance.
Basically what I am trying to do is : For each Agent there one or more role. I trying a lot of thing and I follow documentation but I am not able to do it.
First of all, your role field should not be declared in the Meta class but in the ModelSerializer directly:
class RegistrationSerializer(serializers.ModelSerializer):
role = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Agent
fields = ['email', 'firstName', 'lastName', 'role',
'phoneNumber', 'experienceWorkYeares'],
extra_kwargs = {
'password': {'write_only': True},
}
Then, if you want to write a ManyToManyField you should use something like:
my_role = Role.objects.get(pk=self.validated_data['role'])
agent.role.add(my_role)
Another thing you could do is add a validate_role(self, value) method in your serializer that checks if the primary key provided is correct, and returns Role.objects.get(pk=value) -- then you would get the Role instance in your validated_data. See this part of DRF doc

Save extra fields to django model apart from the fields which were present in django form(for the same model)

I have a Django model with few fields, In the django forms which is created using that django models. While using the forms.save method I also want to save the extra fields which were not present in Django forms but present in django models.
models.py
class NewProvisionalEmployeeMail(models.Model):
STATUS_CHOICES = (
(1, ("Permanent")),
(2, ("Temporary")),
(3, ("Contractor")),
(4, ("Intern"))
)
PAY_CHOICES = (
(1, ("Fixed")),
(2, ("Performance Based")),
(3, ("Not Assigned")),
)
POSITION_CHOICES = ()
for i, name in enumerate(Position.objects.values_list('position_name')):
POSITION_CHOICES += ((i, name[0]),)
email = models.EmailField(max_length=70, null=False, blank=False, unique=False)
token = models.TextField(blank=False, null=False)
offer_sent_by = models.CharField(max_length=50)
position_name = models.IntegerField(choices=POSITION_CHOICES, null=True, blank=True)
accepted = models.BooleanField(default=False)
name=models.CharField(max_length=30)
user_name=models.CharField(max_length=30)
pay = models.IntegerField(default=0)
title = models.CharField(max_length=25, null=True, blank=True)
pay_type = models.IntegerField(choices=PAY_CHOICES, default=3)
emp_type = models.IntegerField(choices=STATUS_CHOICES, default=1)
def __str__(self):
return str(self.offer_sent_by) +" to " + str(self.email)
def clean(self):
if(NewProvisionalEmployeeMail.objects.filter(email=str(self.email)).exists()):
NewProvisionalEmployeeMail.objects.filter(email=str(self.email)).delete()
def save(self, **kwargs):
self.clean()
return super(NewProvisionalEmployeeMail, self).save(**kwargs)
If you see it has following fields :
email, token, offer_sent_by, position_name, accepted, name, user_name, pay, title, pay_type, emp_type.
Now I only want the following fields in my forms :
email, position_name, name, user_name, pay, title, pay_type, emp_type and not token and offer_sent_by whose values will be determined in my views.py using some logic.
forms.py
class NewProvisionalEmployeeMailForm(ModelForm):
class Meta:
model = NewProvisionalEmployeeMail
fields = ['email', 'position_name',
'name', 'user_name', 'pay',
'title', 'pay_type', 'emp_type',
]
According to my logic in my views.py the other fields values are generated inside the function, but since to save the model we have to use formname.save, here it is NewProvisionalEmployeeMailForm.save(). However this will only save the fields which were coming from my template form, how do I also add other left fields while saving using this dunction.
views.py
def sendoffer(request):
context = {}
new_emp_form = NewProvisionalEmployeeMailForm();
context['form'] = new_emp_form
hostname = request.get_host() + "/dummyoffer"
if request.method=='POST':
new_emp_form = NewProvisionalEmployeeMailForm(request.POST)
if(new_emp_form.is_valid()):
token = VALUE COMES FROM LOGIC
offer_sent_by = VALUE COMES FROM LOGIC
# I also want to save the fields token, offer_sent_by in my models using this form save method
new_emp_form.save()
return render(request, 'mainapp/offer.html',context)
As you see new_emp_form save method will only save only those fields that are present in the form and not the fields token and offer_sent_by which is also part of the model. How do save the fields using form.save method?
Saving the form returns an instance of NewProvisionalEmployeeMail, so you can simply catch the returned object in a variable and set it's properties afterwards:
if(new_emp_form.is_valid()):
token = VALUE COMES FROM LOGIC
offer_sent_by = VALUE COMES FROM LOGIC
new_emp = new_emp_form.save(commit=False)
new_emp.token = token
new_emp.offer_sent_by = offer_sent_by
new_emp.save()
for the first time we can change it as following
new_emp = new_emp_form.save(commit=False)
so that i wont save to the database.

Django Rest Framework - perform Put request by passing a JSON object instead of just ID

I'm working on a Project using Python(3), Django(1.11) and DRF(3.6) in which I have to perform a PUT request by passing a nested nested instead of an ID.
Here's What I have tried:
models.py:
class Actor(models.Model):
id = models.CharField(primary_key=True, max_length=255)
login = models.CharField(max_length=255)
avatar_url = models.URLField(max_length=500)
class Repo(models.Model):
id = models.CharField(primary_key=True, max_length=255)
name = models.CharField(max_length=255)
url = models.URLField(max_length=500)
class Event(models.Model):
id = models.CharField(primary_key=True, max_length=255)
type = models.CharField(max_length=255)
actor = models.ForeignKey(Actor, related_name='actor')
repo = models.ForeignKey(Repo, related_name='repo')
created_at = models.DateTimeField()
serializers.py:
class ActorSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = ('id', 'login', 'avatar_url')
class RepoSerializer(serializers.ModelSerializer):
class Meta:
model = Repo
fields = ('id', 'name', 'url')
class EventModelSerializer(serializers.ModelSerializer):
actor = ActorSerializer(many=False)
repo = RepoSerializer(many=False)
class Meta:
model = Event
fields = ('id', 'type', 'actor', 'repo', 'created_at')
depth = 1
def create(self, validated_data):
return Event.objects.create(**validated_data)
Update: Here when I submit a post request with the following object:
{
"id":ID,
"type":"PushEvent",
"actor":{
"id":ID,
"login":"daniel33",
"avatar_url":"https://avatars.com/2790311"
},
"repo":{
"id":ID,
"name":"johnbolton/exercitationem",
"url":"https://github.com/johnbolton/exercitationem"
},
"created_at":"2015-10-03 06:13:31"
}
it return this error as: TypeError: 'ValueError: Cannot assign "OrderedDict([('id', '2790311'), ('login', 'daniel33'), ('avatar_url', 'https://avatars.com/2790311')])": "Event.actor" must be a "Actor" instance.
views.py:
class Actor(generics.GenericAPIView):
serializer_class = EventModelSerializer
queryset = EventModel.objects.all()
def update(self):
actor = EventModel.objects.filter(actor_id=self.request.data('id'))
print(actor)
return HttpResponse(actor)
Sample Input Object:
{
"id":3648056,
"login":"ysims",
"avatar_url":"https://avatars.com/modified2"
}
The requirements is:
Updating the avatar URL of the actor: The service should be able to update the avatar URL of the actor by the PUT request at /actors. The actor JSON is sent in the request body. If the actor with the id does not exist then the response code should be 404, or if there are other fields being updated for the actor then the HTTP response code should be 400, otherwise, the response code should be 200.**
I'm little bit confused about how to perform the PUT request without
passing an ID?
I have seen your two-three questions asked today. I think You are asking the wrong question. I think what you need is three models Event, actor and repo. the event model has two foreign key fields as actor and repo. Now what you want it to update the actor models avtar_url field. OK?
class Actor(models.Model):
avtar_url = models.CharField(max_length=255)
# Other Fields
class Repo(models.Model):
# Other Fields
class EventModel(models.Model):
id = models.CharField(primary_key=True, max_length=255)
type = models.CharField(max_length=255)
actor = models.ForaignKey(Actor)
repo = models.ForaignKey(Actor)
created_at = models.DateTimeField()
Now for create and update the NESTED EventModel entry use writable-nested-serializers. By this, you can directly update the avatar_url for Actor by its id.
UPDATE as per Request
you need to change your create method as following so that it creates Actor, Repo and link their ids to Event
def create(self, validated_data):
actor = validated_data.pop('actor')
repo = validated_data.pop('repo')
actor = Actor.objects.create(**actor)
repo = Repo.objects.create(**repo)
return Event.objects.create(actor=actor,repo=repo,**validated_data)

How to retrieve object attributes of an OneToOneField relation in Django-Rest-Framework

I'm trying to extend the User model with a OneToOneField, so I can add more fields to a user:
class Userattribs(models.Model):
user = models.OneToOneField(User)
passcode = models.IntegerField(default=0)
# about user
organisation = models.CharField(max_length=100, null=True)
description = models.CharField(max_length=300, null=True)
I also have the following model serializers:
class UserattribsSerializer(serializers.ModelSerializer):
class Meta:
model = Userattribs
fields = ('organisation', 'description')
class UserSerializer(serializers.ModelSerializer):
userattribs = UserattribsSerializer(required=True)
class Meta:
model = User
fields =('id', 'username', 'first_name', 'last_name', 'email', 'userattribs')
The problem I'm having is the serialisation of a User, does not include the 'userattribs' in the json response. I've spent hours googling and banging my head. I'd be eternally grateful if you help me out.
Thanks in advance!
I've added the views. One odd thing I noticed was that when I queried (using UserList) all data, I would get the Userattribs. However, when I retrieve a single item, I don't get the Userattribs
###################################################
### Views #########################################
###################################################
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (permissions.AllowAny,)
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (access_permission,)
I don't see any error in your code. Are you sure that your User objects have an Userattribs object.
Open your python shell with python manage.py shell and enter this to test your code :
#import your related models and serializer
from your_app.models import *
from your_app.serializer import *
# create new user
user = User.objects.create(username="test")
# create new user attrib linked to `user` object
user_attrib = Userattribs.objects.create(user=user, organisation="DJANGO")
# serialize your `user` object
user_serializer = UserSerializer(user)
# display your serialized data
user_serializer.data
# outputs : {'last_name': '', 'userattribs': OrderedDict([('organisation', 'DJANGO'), ('description', None)]), 'email': '', 'username': 'test', 'first_name': '', 'id': 3}
If it ok, the problem maybe come from your view.

Categories