I'm receiving the following error when trying to make a POST request to create a particular model within the sqlite3 database:
"<Article>" needs to have a value for field "article" before this many-to-many relationship can be used.
I'm building a REST API using the Django REST Framework and I have two models with a many-to-many relationship as follows:
class Publication(models.Model):
title = models.CharField(max_length=30)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
The corresponding extract in views.py for an Article is as follows:
class ArticleList(mixins.ListModelMixin, mixins.CreateModelMixin):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
I'm assuming the issue is that the article doesn't get saved before the POST request is finished as I'm able to perform all CRUD operations fine using Django's database API.
The PublicationSerializer is as follows:
class PublicationSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=True, max_length=120, allow_blank=False)
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.save()
return instance
def create(self, validated_data):
return Publication.objects.create(**validated_data)
The ArticleSerializer is as follows:
class ArticleSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
headline = serializers.CharField(required=True, max_length=120, allow_blank=False)
publications = serializers.StringRelatedField(many=True)
def update(self, instance, validated_data):
instance.headline = validated_data.get('headline', instance.headline)
instance.publications = validated_data.get('publications', instance.publications)
instance.save()
return instance
def create(self, validated_data):
return Article.objects.create(**validated_data)
A sample of the data I attempted to post:
{
"headline": "Trending Topics",
"publications": []
}
What's the best approach to solving this issue?
Related
I have a model called Client with user field as a foreign key:
class Client(models.Model):
name = models.CharField(_('Client Name'), max_length=100)
address = models.CharField(_('Client Address'), max_length=100, blank=True)
demand = models.PositiveIntegerField(_('Client Demand'))
location = models.PointField(_('Client Location'))
created_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
class Meta:
default_permissions = ('add', 'change', 'delete', 'view')
def __str__(self):
return self.name
I want to limit the choice of the user field in the admin form based on who logged in
for example, here I logged in as agung, so I want the select box choice of user field limit only to agung, but here I can access other username like admin and rizky.
I tried this
class ClientAdminForm(forms.ModelForm):
class Meta:
model = Client
fields = "__all__"
def __init__(self, request, *args, **kwargs):
super(ClientAdminForm, self).__init__(request, *args, **kwargs)
if self.instance:
self.fields['user'].queryset = request.user
but it seems that it can't take request as an argument (I guess because this is not an Http request)
You can overwrite your Admin Model's get_form method to add the current request.user as class property. Next you can read it in the Form's constructor and filter the query.
class ClientAdmin(admin.ModelAdmin):
# [...]
def get_form(self, request, obj=None, **kwargs):
form_class = super(ClientAdmin, self).get_form(request, obj, **kwargs)
form_class.set_user(request.user)
return form_class
class ClientAdminForm(forms.ModelForm):
# [...]
#classmethod
def set_user(cls, user):
cls.__user = user
def __init__(self, *args, **kwargs):
super(ClientAdminForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = \
self.fields['user'].queryset.filter(pk=self.__user.pk)
However, is easiest exclude this field in form and update it in the save_model method:
class ClientAdmin(admin.ModelAdmin):
# [...]
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
You can do it by override the base_fields attribute of your form instance like this :
views.py
# Before instantiate the form class
ClientAdminForm.base_fields['user'] = forms.ModelChoiceField(queryset=self.request.user)
# Now you can instantiate the form
form = ClientAdminForm(...)
NB : Do override the base_fields just before instantiate the form
I'm trying to restrict access of CRUD pages to the owners, but I can't find the class-based view equivalent of "if request.user != post.author raise Http404". Thx for your time.
models.py
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
views.py
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_edit.html'
login_url = 'login'
I tried the following (and many other combination arround those lines), but it isn't working.
def get(self, request, *args, **kwargs):
if self.request.user == self.obj.author:
raise Http404()
Youn can do something like this:-
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_edit.html'
login_url = 'login'
def get(self, request, *args, **kwargs):
self.obj = self.get_object()
if self.request.user != self.obj.author:
raise Http404()
return super(ArticleUpdateView, self).get(request, *args, **kwargs)
I think you can override the get_queryset method to achieve this. For example:
class ArticleUpdateView(...):
def get_queryset(self):
queryset = super(ArticleUpdateView, self).get_queryset()
return queryset.filter(author = self.request.user)
So, when a user tries to update an post which is not created by him, then he will not be able to get it because will not be able find the post object in Queryset provided by get_queryset method. For details, please SingleObjectMixin which is later sub-classed by UpdateView. FYI you don't need to override the get method for this implementation.
i get this error when i am trying to update modified_by field
Tried to update field sales.CustomersTag.modified_by with a model instance, <SimpleLazyObject: <UserProfile: Admin>>. Use a value compatible with CharField.
this is my serializer.py:
class CustomersTagSerializer(serializers.ModelSerializer):
created_by = serializers.CharField(read_only=True, default=serializers.CurrentUserDefault())
modified_by = serializers.CharField(read_only=True, default=serializers.CurrentUserDefault())
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.modified_by = validated_data.get('modified_by', instance.modified_by)
instance.save()
return instance
class Meta:
model = models.CustomersTag
fields = (
'id',
'name',
'created_date',
'modified_date',
'created_by',
'modified_by',
)
and this my view.py:
class CustomerTagGetIdPutView(generics.RetrieveAPIView,
mixins.UpdateModelMixin):
permission_classes = (AllowAny,)
queryset = models.CustomersTag.objects.all()
serializer_class = CustomersTagSerializer
def get_object(self):
id = self.kwargs['id']
obj = generics.get_object_or_404(models.CustomersTag, id=id)
return obj
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
i tried alot to solve this problem but i can't .. any one can help me for this problem
If you're using Django REST Framework generic views and no overriding the behavior of methods like get_serializer or get_serializer_context, your serializer will receive a context object. This context object is a dictionary with the request and the view object.
That said, you can do this by overriding create() and update() in your serializer. For example:
class CustomersTagSerializer(serializers.ModelSerializer):
class Meta:
model = models.CustomersTag
fields = (
'id',
'name',
'created_date',
'modified_date',
'created_by',
'modified_by',
)
def create(self, validated_data):
user = self.context['request'].user
return models.CustomersTag.objects.create(
created_by=user, **validated_data)
def update(self, instance, validated_data):
user = self.context['request'].user
instance.name = validated_data.get('name', instance.name)
instance.modified_by = user
instance.save()
return instance
But maybe if you want to maintain a log history of editions in your models you could use a package like django-auditlog.
You can do this while calling save() in your model.
For example:
class CustomersTagSerializer(serializers.ModelSerializer):
created_by = models.ForeignKey(User, null=True, editable=False)
modified_by = models.ForeignKey(User, null=True, editable=False)
def save(self, *args, **kwargs):
user = get_current_user()
if user and user.is_authenticated():
self.modified_by = user
if not self.id:
self.created_by = user
super(CustomersTagSerializer, self).save(*args, **kwargs)
I have a model Course that has the following attr:
class Course(models.Model):
user= models.ForeignKey(
User,
on_delete=models.CASCADE,
)
# email= models.EmailField(default=user.email)
courseName= models.CharField(max_length=20, blank=False)
class Meta:
unique_together= ('user','courseName',)
def __str__(self):
return self.courseName
I have created a form where I want the user to enter just the courseName and after they POST it, I will add the requested user in the model as well.
This is my form which is getting passed on to the template via my ListView
forms.py
class CourseForm(forms.ModelForm):
class Meta:
model= Course
fields = ['courseName']
**Here is my views.py where I am struggling with **
class CoursesListView(ListView, FormMixin):
model = Course
form_class = CourseForm
template_name = "userApp/courseList.html"
def get_queryset(self):
return Course.objects.filter(user__exact=self.request.user)
def get_context_data(self,*args,**kwargs):
context= super(CoursesListView,self).get_context_data(*args, **kwargs)
context['courseForm'] = self.form_class
return context
def post(self,request, *args, **kwargs):
form = self.form_class(request.POST)
user = User.objects.get(username__exact=self.request.user)
**I want to add the user to my model.user field here**
return self.get(redirect, *args, **kwargs)
def get(self,request, *args, **kwargs):
self.object=None
self.form = self.get_form(self.form_class)
return ListView.get(self, request, *args, **kwargs)
So basically my question is how can I add the user in my model before calling form.is_valid().
something like this?
def post(self,request, *args, **kwargs):
form_data = copy.copy(request.POST)
form_data['user'] = User.objects.get(username__exact=self.request.user).pk
form = self.form_class(form_data)
# form handling follows
return self.get(redirect, *args, **kwargs)
This Answer was suggested by a user who deleted this answer. Didnt get his user id but whoever you were thanks a lot for the help!!!
Just use form.save(commit=False) and then make the necessary changes.
def post(self,request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
user = User.objects.get(username__exact=self.request.user)
instance = form.save(commit=False)
instance.user = user
instance.save()
I am learning Django and trying to create a Game REST API using Django REST framework.I am not sure how to update the publisher field.Do I need to create another Serializer for my Publisher class and implement a save method?
I am using POSTMAN to send a PUT request and I am recieving this:
"publisher": [
"Incorrect type. Expected pk value, received unicode."
]
when I send a JSON request
{
"name": "Test",
"release_date": "1979-01-01",
"rating": 5,
"comment": "Terrible Game!",
"publisher": "Me"
}
models.py
class Publisher(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class Game(models.Model):
name = models.CharField(max_length=100)
release_date = models.DateField(null=True, blank=True)
rating = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(0), MaxValueValidator(5)])
comment = models.TextField(null=True, blank=True)
publisher = models.ForeignKey(Publisher, null=True, blank=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('name',)
serializers.py
from rest_framework import serializers
from game.models import Game, Publisher
class GameSerializer(serializers.ModelSerializer):
publisher = serializers.PrimaryKeyRelatedField(allow_null=True, queryset=Publisher.objects.all(), required=False)
class Meta:
model = Game
fields = ('pk','name', 'release_date', 'rating', 'comment', 'publisher')
def create(self, validated_data):
"""
Create and return a new 'Game' instance, given the validated data.
"""
return Game.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing 'Game' instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.release_date = validated_data.get('release_date',instance.release_date)
instance.rating = validated_data.get('rating', instance.rating)
instance.comment = validated_data.get('comment', instance.comment)
instance.publisher = validated_data.get('publisher', instance.publisher)
instance.save()
return instance
views.py
class GameDetail(APIView):
"""
Retrieve, update or delete a game instance.
"""
def get_object(self,pk):
try:
return Game.objects.get(pk=pk)
except Game.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
game = self.get_object(pk)
serializer = GameSerializer(game)
return Response(serializer.data)
def put(self, request, pk, format=None):
game = self.get_object(pk)
serializer = GameSerializer(game, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
game = self.get_object(pk)
game.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
You're sending a string ("Me") as the publisher attribute. That's why you get your "Incorrect type. Expected pk value, received unicode." error.
Since publisher is a ForeignKey in your Game model, you'll have to get the instance of "Me" Publisher before saving it in your database.
One possible approach is to modify your update() method in your serializer and search for the right instance :
instance.publisher = Publisher.objects.get_or_create(name=validated_data.get('publisher')) and then call instance.save().