Django many-to-many model DRF - python

I have the following model structure:
class Project(models.Model):
author = models.ManyToManyField(Account)
name = models.CharField(max_length=40, default='NewBook')
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=40, unique=True)
first_name = models.CharField(max_length=40, blank=True)
last_name = models.CharField(max_length=40, blank=True)
tagline = models.CharField(max_length=140, blank=True)
is_admin = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = AccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
My view looks like this:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.order_by('-name')
serializer_class = ProjectSerializer
def perform_create(self, serializer):
instance = serializer.save(author=self.request.user)
return super(ProjectViewSet, self).perform_create(serializer)
After calling the view function, a classifier gets created in the database. But after that, I get the following error:
TypeError: 'Account' object is not iterable
The error gets thrown in this line:
instance = serializer.save(author=self.request.user)
Anyone how can help me with this?

There are two problems here:
Showing nested relationships for M2M field:
If the field is used to represent a to-many relationship, you should add the many=True flag to the serializer field.
So you need to add many=True to AccountSerializer:
author = AccountSerializer(read_only=True, required=False, many=True)
A writable nested serializer:
By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved.
So if you look at the example and the documentation it seems that you need to implement create or update method.

You need to set many=True when dealing with multiple relation - either a m2m or a reversed FK:
author = AccountSerializer(read_only=True, required=False, many=True)

Since your Author field is many to many, you will need to override the create method on your serializer.
def create(self, validated_data):
author = validated_data.pop(author, None)
project = Project.objects.save(validated_data)
if author:
project.author.add(author)
You will also probably need to set the update method on the serializer, the behavior here can be tricky so make sure you test and make sure the behavior is what you expect.

Ok, my previous answer, though could be an issue, isn't the root cause of the actual crash.
When calling the serializer, you set:
instance = serializer.save(author=self.request.user)
However, author is a ManyToManyField which means you should call the serializer as:
instance = serializer.save(author=[self.request.user])
NB: you still require the many=True on the serializer's author field.

Please check...
your model.py
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=40, unique=True)
first_name = models.CharField(max_length=40, blank=True)
last_name = models.CharField(max_length=40, blank=True)
tagline = models.CharField(max_length=140, blank=True)
is_admin = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = AccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Project(models.Model):
author = models.ManyToManyField(Account)
name = models.CharField(max_length=40, default='NewBook')
your serializer.py
class ProjectSerializer(serializers.ModelSerializer):
author = AccountSerializer(read_only=True, required=False)
class Meta:
model = Project
fields = ('id', 'author', 'name')
read_only_fields = ('id')
def get_validation_exclusions(self, *args, **kwargs):
exclusions = super(ProjectSerializer, self).get_validation_exclusions()
return exclusions + ['author']
and finally your view.py is
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.order_by('-name')
serializer_class = ProjectSerializer
def perform_create(self, serializer):
instance = serializer.save(author=self.request.user)
return super(ProjectViewSet, self).perform_create(serializer)

Related

How to filter/hide fields in django rest framework HTML form using authenticated user

I created two models:
parcel (package) model,
'shelf' model to which parcels can be assigned.
class Parcel(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
name = models.CharField(max_length=100, blank=False)
contents = models.TextField(blank=False, validators=[MaxLengthValidator(1500)])
size = models.CharField(max_length=50, blank=True, validators=[package_size_validator])
weight = models.PositiveIntegerField(blank=True, null=True)
contact = models.CharField(max_length=50, blank=True)
creation_date = models.DateTimeField(auto_now_add=True)
last_modification = models.DateTimeField(auto_now=True)
code = models.CharField(max_length=30, unique=True, blank=False)
def __str__(self):
return f'Parcel: {self.code}, {self.owner}, {self.name}'
class ParcelShelf(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
name = models.CharField(max_length=100, blank=False)
creation_date = models.DateTimeField(auto_now_add=True)
last_modification = models.DateTimeField(auto_now=True)
parcels = models.ManyToManyField('Parcel', blank=True, related_name='shelf_parcel')
def __str__(self):
return f'ParcelShelf: {self.owner}, {self.name}'
I came to a solution where the logged-in user can see only his packages and shelves. The problem I have is with the many-to-many relationship where parcels can be added to shelves. I want to come to a solution where the logged in user can add to the shelf only those parcels which he is the owner, creator. It will look better in pictures.
All packages created by user t2#t2.com (user id = 17):
parcels list
Now the view when the user t2#t2.com wants to create a shelf:
shelf list
All packages are visible, while only those created by the user t2#t2.com should be available.
Code to serializer:
class ParcelShelfSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
parcels = serializers.HyperlinkedRelatedField(many=True, read_only=False, view_name='parcels_detail_view',
# queryset=Parcel.objects.filter(owner=17)
queryset=Parcel.objects.all()
)
class Meta:
model = ParcelShelf
fields = ('id', 'owner', 'name', 'creation_date', 'last_modification', 'parcels')
Below is a picture where only packages for a given, logged-in user are available:
shelf list
Code to serializer:
class ParcelShelfSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
parcels = serializers.HyperlinkedRelatedField(many=True, read_only=False, view_name='parcels_detail_view',
queryset=Parcel.objects.filter(owner=17)
# queryset=Parcel.objects.all()
)
class Meta:
model = ParcelShelf
fields = ('id', 'owner', 'name', 'creation_date', 'last_modification', 'parcels')
I got to the point where the 'solution' is in the 'queryset' argument.
All users: queryset=Parcel.objects.all()
Logged in user: queryset=Parcel.objects.filter(owner=17)
The problem is, this is hardcoded, and it should be something like: (owner=request.user). Unfortunately, I don't know how to achieve this in the serializer. I looked through other similar topics, but I didn't find a solution how to use the request method in the serializer field.
In addition, code in views:
class ParcelsShelfList(generics.ListCreateAPIView):
# queryset = ParcelShelf.objects.all()
serializer_class = ParcelShelfSerializer
def get_queryset(self):
user = self.request.user
if bool(user and user.is_staff and user.is_admin):
return ParcelShelf.objects.all()
else:
return ParcelShelf.objects.filter(owner=user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
UPDATE
Thanks to the help of #Amrez, who gave me a link to a similar topic, i was able to do it.
mentioned link: How can I filter DRF serializer HyperlinkedRelationField queryset based on request data?
I add this to my code in serializers.py:
def hyperlinked_related_field_by_owner(model, view_name, owner):
return serializers.HyperlinkedRelatedField(
many=True,
view_name=view_name,
queryset=model.objects.filter(owner=owner)
)
class ParcelShelfSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
parcels = serializers.HyperlinkedRelatedField(many=True,
read_only=False,
view_name='parcels_detail_view',
# queryset=Parcel.objects.filter(owner=17)
queryset=Parcel.objects.all()
)
def get_fields(self):
fields = super().get_fields()
owner = self.context['request'].user
fields['parcels'] = hyperlinked_related_field_by_owner(Parcel, 'parcels_detail_view', owner)
return fields
class Meta:
model = ParcelShelf
fields = ('id', 'owner', 'name', 'creation_date', 'last_modification', 'parcels')

Why unable to get related data in Self-referencing many-to-many in Django?

I and new to django and working on a small application containing a model nameCustomUser.CustomUser is having ManyToMany relationship with itself, i have implemented feature, that a user can follow another user.But when i am trying to fetch all the users which current authenticated is following i am not getting the desired result.
Models:-
class CustomUser(AbstractUser):
email = models.EmailField(max_length=250, null=False, unique=True)
name = models.CharField(max_length=50, null=False)
username = models.CharField(max_length=50, null=False)
password = models.CharField(max_length=15, null=False)
user = models.ManyToManyField('self', through='Relationship', symmetrical=False, related_name='related_to')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'username']
def get_all_followings(self):
print("print {}".format(self))
print("all followings {}".format(self.to_person))
class Relationship(models.Model):
from_person = models.ForeignKey(CustomUser, related_name='from_people', on_delete=models.CASCADE)
to_person = models.ForeignKey(CustomUser, related_name='to_person', on_delete=models.CASCADE)
from_person means who is following, and to_person means to whome he is following.for example in my case deep is currently authenticated user, and he is following to ram.
So if i tried to print Relationship object i got following output:
{'_state': <django.db.models.base.ModelState object at 0x00000273836034C0>, 'id': 1, 'from_person_id': 2, 'to_person_id': 4}
view:-
def see_all_followings(request):
if request.method == 'GET':
current_user = CustomUser.objects.get(id=request.user.id)
all_followings = current_user.get_all_followings()
# return render(request, "all_followings.html", {'users':, 'is_follow': True})
OutPut which i got:-
Quit the server with CTRL-BREAK.
print deep#gmail.com
all followings user.Relationship.None # But user is following one user..
Thanks in advance..
Hope to here from you soon..
You retrieve a manager, not the queryset, you need to use .all() [Django-doc] to obtain the QuerySet the manager is managing, so:
class CustomUser(AbstractUser):
# &vellip;
def get_all_followings(self):
print(f'print {self}')
print(f'all followings {self.to_person.all()}')
In your model you can specify the relation with:
class CustomUser(AbstractUser):
# &vellip;
following = models.ManyToManyField(
'self',
through='Relationship',
through_fields=('from_person', 'to_person'),
symmetrical=False,
related_name='related_to'
)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'username']
Then you can access the follwers with:
current_user.following.all()

django all fields not appearing in admin form

I have following model in django:
class ProjectScheme(models.Model):
name = models.CharField(max_length=250, blank=False,null=False)
parent_scheme_id = models.ForeignKey(ProjectSchemeMaster, on_delete = models.CASCADE)
rule = models.TextField(blank=True)
created_on = models.DateTimeField(auto_now=False, auto_now_add=True)
created_by = models.IntegerField(blank=True,null=True)
updated_on = models.DateTimeField(auto_now=True, auto_now_add=False)
updated_by = models.IntegerField(blank=True,null=True)
def __str__(self):
return str(self.name)
And in admin.py
# Register your models here.
class ProjectSchemeAdmin(admin.ModelAdmin):
exclude = ('id',)
class Meta:
model = ProjectScheme
admin.site.register(ProjectScheme, ProjectSchemeAdmin)
But date fields: updated_on, created_on don't show up on admin form.
I also tried without ProjectSchemeAdmin class, and also:
class ProjectSchemeAdmin(admin.ModelAdmin):
pass
Also,
# list_display = [field.name for field in ProjectScheme._meta.fields if field.name != "id"]
list_display = ["id", "name","parent_scheme_id","rule","created_on", "created_by","updated_on","updated_by"]
But the same.
I need to get all the fields in admin form.
You need to add readonly_fields = ('created_on','updated_on') to display the date field.
# Register your models here.
class ProjectSchemeAdmin(admin.ModelAdmin):
list_display = ["name","parent_scheme_id","rule","created_on", "created_by","updated_on","updated_by"]
readonly_fields = ('created_on','updated_on')
class Meta:
model = ProjectScheme
admin.site.register(ProjectScheme, ProjectSchemeAdmin)
As mentioned in the docs:
As currently implemented, setting auto_now or auto_now_add to True will cause the field to have editable=False and blank=True set.
The value auto_now_add is True. So no matter what date value you enter via admin UI, it will ultimately be overriden, which means there is no reason to display the field. In this case, as you need to display the field anyway, adding the readonly_fields does the trick.
Hope this helps!

JSON data not being sent - Django Rest Framework / React

So, I'm new to Django and after hours of searching / trying different things I can't figure this out.
I have a form that submits my components state to the api, adds the values to the database, and then displays them in a table. Everything is getting into the database except for the "projects" field. When I look in the React Dev tools, the value I expect is in my state.
project: 3
But after I submit the form the returned value shows as null.
project: null
I'm really not sure why the value is null.
Here is my models.py
class Completed(models.Model):
completed = models.BooleanField(default=False)
url = models.CharField(max_length=100)
handle = models.CharField(max_length=30)
filename = models.CharField(max_length=100)
size = models.IntegerField()
source = models.CharField(max_length=50)
uploadId = models.CharField(max_length=50)
originalPath = models.CharField(max_length=50)
owner = models.ForeignKey(
User, related_name="completed", on_delete=models.CASCADE, null=True)
project = models.ForeignKey(
Project, related_name="projectId", on_delete=models.CASCADE, null=True)
uploadDate = models.DateTimeField(auto_now_add=True)
Here is the Project model
class Project(models.Model):
projectCode = models.CharField(max_length=10)
projectName = models.CharField(max_length=100)
user = models.ForeignKey(
User, related_name="projects", on_delete=models.CASCADE, null=True)
editor = models.ForeignKey(
User, on_delete=models.CASCADE, null=True)
creationDate = models.DateTimeField(auto_now_add=True)
completedDate = models.DateTimeField(null=True, blank=True)
dueDate = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"{self.projectName}"
Here are the serializers
# Project Serializer
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = [
'id',
'projectCode',
'projectName',
'user'
]
depth = 1
# Completed Serializer
class CompletedSerializer(serializers.ModelSerializer):
# project = ProjectSerializer()
class Meta:
model = Completed
fields = [
'completed',
'url',
'handle',
'filename',
'size',
'source',
'uploadId',
'originalPath',
'owner',
'project',
'uploadDate'
]
depth = 1
I tried adding the ProjectSerializer into the CompletedSerializer but it gave me a 400 bad-request error.
And here is the viewset
class CompletedViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CompletedSerializer
def get_queryset(self):
queryset = self.request.user.completed.all().filter(completed=True)
return queryset
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Can anyone tell me what I'm doing wrong here?
You're using depth=1 on your serializers. That means you'll have nested serializer for your ForeignKey. By default, nested serializers are read-only. If you want a writable nested serializer, you have to uncomment your # project = ProjectSerializer() on CompletedSerializer and pass all the data for the creation of a project instance. I don't think this is what you want. If I've understood well, you only want to reference an existing project. So, the best way to accomplish that, in my opinion, is removing the depth on your CompletedSerializer. If you need the project when you list or retrieve a Completed instance, go with a different serializer based on the request action. Here more details about writable-nested serializers https://www.django-rest-framework.org/community/3.0-announcement/#writable-nested-serialization.
For using different serializers, this is a reference. Add this code in your ViewSet:
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CompletedNestedSerializer
return CompletedSerializer

Alllow one extra filed into serializer and return validated data with that field in Django Rest Framework

I know on this topic people asked a question before but my case is different and I have implemented almost all the solutions which I found but none of them are worked for me.
First I have defined three classes in models:
models.py
class User(AbstractBaseUser, PermissionsMixin):
""" User Model """
username = None
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
agency = models.ForeignKey('agency.Agency', on_delete=models.CASCADE, null=True)
weekly_email = models.NullBooleanField()
is_create_new_password = models.NullBooleanField(default=True)
is_active = models.BooleanField(default=True)
last_login_time = models.DateTimeField(null=True)
last_login_from = models.CharField(max_length=255, null=True)
created_at = models.DateField(default=timezone.now)
updated_at = models.DateField(default=timezone.now)
created_by = models.IntegerField(null=True)
updated_by = models.IntegerField(null=True)
""" The `USERNAME_FIELD` property tells us which field we will use to log in.
In this case, we want that to be the email field. """
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
""" Tells Django that the UserManager class defined above should manage
objects of this type. """
objects = UserManager()
class Role(models.Model):
""" Role Model """
name = models.CharField(max_length=255, unique=True)
class UserRole(models.Model):
""" User Role Model """
user = models.ForeignKey(User, on_delete=models.CASCADE)
role = models.ForeignKey(Role, on_delete=models.CASCADE)
Then I have defined my serializer for user module:
serializers.py
class RegistrationSerializer(serializers.ModelSerializer):
""" Serializers registration requests and creates a new user. """
user_id = serializers.IntegerField(required=False)
email = serializers.EmailField(max_length=255)
name = serializers.CharField(max_length=255)
agency_id = serializers.IntegerField(source='agency.id', required=False)
role = serializers.CharField(source='role.name')
weekly_email = serializers.NullBooleanField()
last_login_time = serializers.DateTimeField(required=False)
last_login_from = serializers.CharField(max_length=255, required=False)
class Meta:
model = User
fields = (
'role', 'user_id', 'email', 'name', 'agency_id', 'weekly_email', 'last_login_time',
'last_login_from'
)
And At the end, I have defined my view file for user creation:
views.py
class UserCreateAPIView(APIView):
""" User create Api view class """
#Allow any user (authenticated or not) to hit this endpoint.
permission_classes = (IsAuthenticated,)
serializer_class = RegistrationSerializer
def post(self, request):
""" create user using following logic. """
request.data['user_id'] = request.user.id
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user)
return Response({'message': response['user']['created'], 'data': serializer.data},
status=status.HTTP_201_CREATED)
Now when I run it everything works fine like user is created, role is created as per my expectations. My view, serializer and models excuted but at the end on this line:
return Response({'message': response['user']['created'], 'data': serializer.data},
status=status.HTTP_201_CREATED)
I am facing error like,
AttributeError: Got AttributeError when attempting to get a value for field `role` on serializer `RegistrationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'role'.
I think you need to use ModelSerializer
class RegistrationSerializer(serializers.Serializer):
to
class RegistrationSerializer(serializers.ModelSerializer):

Categories