These are my models : Test, Skillarea, question
MODELS.PY :
class Test(BaseModel):
types = models.ManyToManyField(
TestType,
related_name='tests',
)
title = models.CharField(max_length=255)
summary = models.TextField()
def __str__(self):
return self.title
class SkillArea(BaseModel):
title = models.CharField(max_length=255)
test = models.ForeignKey('Test', on_delete=models.PROTECT, related_name='skill_areas')
questions = models.ManyToManyField(
'assessment.Question',
related_name='skill_areas',
)
def __str__(self):
return self.title
class Question(BaseModel):
question_text = models.TextField()
def get_absolute_url(self):
self.get_type_display()
def __str__(self):
return truncatewords(self.question_text, 7)
class TestType(BaseModel):
title = models.CharField(max_length=255)
def __str__(self):
return self.title
I want to have an updateserializer for updating, but the field "type" in the Test model, only can be updated if there is no question in Skillarea model which related to Test model( has the same id as updating test, in its test field)
I wrote these serializer and view but it doesnt know data['id'] which i used in validator and sends KeyError: 'id'
serializer.py :
class TestUpdateAPIViewSerializer(serializers.ModelSerializer):
def validate(self, data):
questions = SkillArea.objects.filter(test=data['id'], questions__isnull=False)
if questions.exists():
raise serializers.ValidationError("You may not edit type")
return data
class Meta:
model = Test
fields = (
'id',
'types',
'title',
'summary',
)
Views.py :
class TestUpdateAPIView(APIView):
def patch(self, request, pk):
test = Test.active_objects.get(pk=pk)
serializer = TestUpdateAPIViewSerializer(instance=test, partial=True, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)
I think you can get the id from instance attribute.
class TestUpdateAPIViewSerializer(serializers.ModelSerializer):
def validate(self, data):
questions = SkillArea.objects.filter(test=self.instance.id, questions__isnull=False)
...
Related
So im trying to add a new field to my serializer by using SerializerMethodField(), but it is not showing. How come?
models.py:
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE) ##
content = models.TextField(max_length=400, blank=False)
date_posted = models.DateTimeField(auto_now_add=True)
#property
def username(self):
return self.author.username
def __int__(self):
return self.id
serializers.py:
class PostSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
class Meta:
model = Post
fields = '__all__'
def get_username(self, obj):
return obj.username
#return obj.author.username dooesnt work either
view.py where i go to see the details of a post;
#api_view(['GET'])
def postDetail(request, pk):
post = Post.objects.get(id=pk)
serializer = PostSerializer(post, many=False)
return Response(serializer.data)
and this is what turns up, username field is missing:
{
"id": 2,
"content": "6",
"date_posted": "08/12/2020 18:33:55",
"author": 1
}
I wanted to get the date or tablerequireDate argument from the POST requests, how do I achieve in the below view layer ?
VIEWS.PY
class Tablecreateview(generics.CreateAPIView):
queryset = Tables.objects.all()
serializer_class = Tableserializer
def perform_create(self, serializer):
request_user = self.request.user
serializer.save(author=request_user)
MODELS.PY
class Tables(models.Model):
tablerequiretime = models.TimeField()
tablerequireDate = models.DateField()
created = models.DateTimeField(auto_now=True)
updatedat = models.DateTimeField(auto_now_add=True)
foodliketoeat = models.CharField(max_length=200)
totalpersons = models.PositiveIntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(20)])
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.author.username
SERIALIZERS.PY
class Tableserializer(serializers.ModelSerializer):
class Meta:
model = Tables
exclude=('author',)
Okay you can access this field before perform_create takes place
class Tablecreateview(generics.CreateAPIView):
queryset = Tables.objects.all()
serializer_class = Tableserializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
tablerequireDate = serializer.data['tablerequireDate']
# Do you magic then perform creation
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Another Round of a solution
class Tableserializer(ModelSerializer):
class Meta:
model = Tables
exclude=('author',)
def validate_tablerequireDate(self, data):
try:
# do validation
except ValidationError:
raise Validation.....
else:
return validated_data
I can't save model with Foreignkey field.
Thanks to "azudo" problem solved. Solution below
For example I have simple models:
class User(AbstractUser):
class Meta:
pass
email_validator = EmailValidator()
username = models.CharField('Name', max_length=150, )
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField('Email', blank=True, unique=True, validators=[email_validator], )
...
class Package(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='packages')
description = models.CharField('Description', max_length=256, default='description')
weight = models.CharField('weight', max_length=256, default='weight')
...
View (the user is guaranteed to be in the request):
#api_view(["POST"])
def test(request):
data = request.data
data['user'] = User.objects.get(id=request.user.id)
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
My serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PackageSerializer(ModelSerializer):
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def to_representation(self, instance):
self.fields['user'] = UserSerializer(many=False, read_only=True)
self.fields['where_from'] = LocationSerializer(many=False, read_only=True)
self.fields['destination'] = LocationSerializer(many=False, read_only=True)
return super().to_representation(instance)
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user, **validated_data)
return package
json in request:
{
"description": "Some package",
"weight": "12",
}
So, I'have user in database, and want create package for him. But in overridden create in PackageSerializer, validated_data doesn't have user. Please explain what I'm doing wrong.
Versions of django and drf:
django==2.2.4
djangorestframework==3.10.2
Solution:
Serializer:
class PackageSerializer(ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user)
return package
View:
#api_view(["POST"])
def create_package(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
DRF will ignore included fields that are marked as read-only so the caller cannot include read-only data. If you want to include additional attributes simply pass them as keyword args to save:
https://www.django-rest-framework.org/api-guide/serializers/#passing-additional-attributes-to-save
e.g.
#api_view(["POST"])
def test(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
I'm new to creating REST API so I might misunderstand something.
I'm creating REST API using Django Rest Framework. And I'm trying to create an object and send it from my mobile app.
However, API returns 400. I think it still cannot associate the object with the request user and I'm wondering how to do it.
models.py
class Item(models.Model):
item_name = models.CharField()
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name', 'created_by')
and views.py
class ListItems(generics.ListCreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
What I want to know is how to associate the object with the request user when posting the object like as we do like
if form.is_valid():
item = form.save(commit=False)
item.created_by = request.user
item.save()
I think the easiest approach is like this:
class ItemSerializer(serializers.ModelSerializer):
created_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
Reference can be found here
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name',)
class ListItems(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
you can do this way
One of the possible way to overwrite serializer_create method. As user is not associated with request.data first we need to make sure, this is write_only field and also need to assign current user from modelSerializer's self.context.request.user. Following addition should solve the problem.
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name', 'created_by')
extra_kwargs = {'created_by': {'write_only': True}}
def create(self, validated_data):
item = Item(
item_name=validated_data['item_name'],
created_by=self.context.request.user
)
item.save()
return item
Reference link
It works for me
models.py
class Category(models.Model):
name = models.CharField('Category', max_length=200, unique=True, help_text='Name of the category')
slug = models.SlugField('Slug', max_length=100, db_index=True, unique=True, help_text='Name of the category in format URL')
def __str__(self):
return (self.name)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = [
'id', 'name', 'slug'
]
read_only_fields = [
'slug',
]
Finally, I get the user in the view, before to save the post.
views.py
class CategoryList(APIView):te a new category instance.
permission_classes = (IsAuthenticatedOrReadOnly,)
def get(self, request, format=None):
categories = Category.objects.all()
serializer = CategorySerializer(categories, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, format=None):
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save(created_by=self.request.user)
Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
After extending an existing user model, I get RelatedObjectDoesNotExist exception with a value User has no dcf_profile. I seems that dcf_profile isn't created automatically for each user.
Please take a look at my model, view and form below and tell me how can I correct my views file?
My models.py :
class CustomUser(models.Model):
auth_user_ptr = models.OneToOneField(
User,
parent_link=True,
related_name='dcf_profile',
primary_key=True
)
phone = models.CharField(_('phone'), max_length=30, null=True, blank=True)
receive_news = models.BooleanField(_('receive news'), default=True, db_index=True)
class Meta:
app_label = 'dcf'
def allow_add_item(self):
if self.item_set.count() > settings.DCF_ITEM_PER_USER_LIMIT:
return False
else:
return True
class Item(models.Model):
slug = models.SlugField(blank=True, null=True, max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, verbose_name=_('group'))
title = models.CharField(_('title'), max_length=100)
description = models.TextField(_('description'))
price = models.DecimalField(_('price'), max_digits=10, decimal_places=2)
phone = models.CharField(_('phone'), max_length=30)
is_active = models.BooleanField(_('display'), default=True, db_index=True)
updated = models.DateTimeField(_('updated'), auto_now=True, db_index=True)
posted = models.DateTimeField(_('posted'), auto_now_add=True)
def __unicode__(self):
return self.title
class Meta:
verbose_name = _('item')
verbose_name_plural = _('items')
ordering = ('-updated', )
def get_absolute_url(self):
return reverse('item', kwargs={
'pk': self.pk,
'slug': self.slug
})
def get_title(self):
return u'%s' % self.title
def get_description(self):
return u'%s' % self.description[:155]
def get_keywords(self):
# TODO need more optimal keywords selection
return ",".join(set(self.description.split()))
def get_related(self):
# TODO Need more complicated related select
return Item.objects.exclude(pk=self.pk)[:settings.DCF_RELATED_LIMIT]
def save(self, *args, **kwargs):
if self.slug is None:
self.slug = slugify(unidecode(self.title))
super(Item, self).save(*args, **kwargs)
My views.py :
class ItemCreateView(FormsetMixin, CreateView):
is_update_view = False
model = Item
form_class = ItemCreateEditForm
formset_class = inlineformset_factory(Item, Image, extra=3, fields=('file', ))
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
if not self.request.user.dcf_profile.allow_add_item():
messages.error(self.request, _('You have reached the limit!'))
return redirect(reverse('my'))
return super(ItemCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form, formset):
form.instance.user = self.request.user
form.save()
return super(ItemCreateView, self).form_valid(form, formset)
My forms.py
class ItemCreateEditForm(forms.ModelForm):
class Meta:
model = Item
fields = ('group', 'title', 'description', 'price', 'phone', 'is_active')
Just to share with you a solution that worked, I have created a proxy model of the User model and updated my views.py :
Updated models.py
class DcfUser(User):
class Meta:
proxy = True
def allow_add_item(self):
if self.item_set.count() > settings.DCF_ITEM_PER_USER_LIMIT:
return False
else:
return True
Updated views.py
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
user = self.request.user
my_user = DcfUser.objects.get(username=user)
if not my_user.allow_add_item():
messages.error(self.request, _('You have reached the limit!'))
return redirect(reverse('my'))
Thank you