I created some app and want to set up different throttling for GET/POST,PUT,PATCH. When I make a GET request, it works fine. But when I perfom POST/PUT/PATCH it looks like cache is restarted. I want to create also extra headers so that's why I think this is actually the problem.
Throttling.py
class SetHeaders:
def throttle_success(self):
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
print(self.key, self.history, self.duration)
print(f"REMAIN TIME {self.duration - (self.now - self.history[-1])}")
print(f'LIMIT {self.get_rate()}')
print(f'REMAIN RATE {self.num_requests - len(self.history) + 1}')
return True
class UserPostRateThrottle(SetHeaders, throttling.UserRateThrottle):
scope = "user_post"
def allow_request(self, request, view):
if request.method == "GET":
return True
return super().allow_request(request, view)
class UserGetRateThrottle(SetHeaders, throttling.UserRateThrottle):
scope = "user_get"
def allow_request(self, request, view):
if request.method in ("POST", "PUT", "PATCH"):
return True
return super().allow_request(request, view)
views.py
class TruckViewSet(viewsets.ModelViewSet):
queryset = Truck.confirmed.all()
serializer_class = TruckSerializer
filterset_class = TruckFilter
permission_classes = (
permissions.DjangoModelPermissions,
IsOwnerOrReadOnly,
)
throttle_classes = (UserGetRateThrottle, UserPostRateThrottle)
def _get_images(self, request):
for img in request.data.getlist("image"):
image_serializer = TruckImageSerializer(data={"image": img})
image_serializer.is_valid(raise_exception=True)
def create(self, request, *args, **kwargs):
if request.data.get("image"):
self._get_images(request)
return super(TruckViewSet, self).create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def update(self, request, *args, **kwargs):
if request.data.get("image"):
self._get_images(request)
return super(TruckViewSet, self).update(request, *args, **kwargs)
serializers.py
class TruckSerializer(serializers.ModelSerializer):
location = LocationSerializer(
read_only=True,
)
owner = serializers.PrimaryKeyRelatedField(
read_only=True,
)
name = serializers.CharField(
max_length=50,
validators=[
validators.UniqueValidator(queryset=Truck.confirmed.all())
],
)
images = serializers.SerializerMethodField()
payment_methods = serializers.SerializerMethodField()
class Meta:
model = Truck
fields = (
"id",
"owner",
"name",
"phone",
"email",
"facebook",
"instagram",
"page_url",
"description",
"city",
"payment_methods",
"images",
"updated",
"location",
)
def _get_payments(self, data):
new_payments = []
payments = data.get("payment_methods")
for payment in payments.split(", "):
try:
filtered_payment = PaymentMethod.objects.get(
payment_name__iexact=payment
).id
except PaymentMethod.DoesNotExist:
raise serializers.ValidationError(
"Given payment method does not match"
)
new_payments.append(filtered_payment)
return new_payments
def create(self, validated_data):
data = self.context.get("view").request.data
new_payments = data.get("payment_methods", {})
if new_payments:
new_payments = self._get_payments(data)
truck = Truck.objects.create(**validated_data)
if data.get("image"):
for image_data in data.getlist("image"):
TruckImage.objects.create(truck=truck, image=image_data)
truck.payment_methods.add(*new_payments)
return truck
def update(self, instance, validated_data):
data = self.context.get("view").request.data
method = self.context.get("view").request.method
new_payments = data.get("payment_methods", {})
if new_payments:
new_payments = self._get_payments(data)
if data.get("image"):
images = instance.images.all()
if images.exists():
instance.images.all().delete()
for image_data in data.getlist("image"):
TruckImage.objects.create(truck=instance, image=image_data)
if method == "PUT" or method == "PATCH" and new_payments:
instance.payment_methods.clear()
instance.payment_methods.add(*new_payments)
return super(TruckSerializer, self).update(instance, validated_data)
def get_images(self, obj):
images = obj.images.all()
return [img.image.url for img in images]
def get_payment_methods(self, obj):
return obj.payment_methods.values_list("payment_name", flat=True)
settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
'rest_framework.throttling.ScopedRateThrottle',
# 'rest_framework.throttling.UserRateThrottle'
],
"DEFAULT_THROTTLE_RATES": {
"user_get": "6/day",
"user_post": "4/day",
# 'user': '5/day'
},
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 10,
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend"
],
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
}
So when GET requested is performed I get:
>>>REMAIN TIME 86399.02145123482
>>>LIMIT 6/day
>>>REMAIN RATE 3
and so on...
But in case of other request i get the same info all the time
>>>REMAIN TIME 86400.0
>>>LIMIT 4/day
>>>REMAIN RATE 4
What i've tried?
I've tried to change to simple UserRateThrottle, but nothing changed. So I think custom throttle is made correctly. Also I was changing between default Django cache/Redis server/Dummy cache and still not working as desired.
Thanks in advance!
Related
When I try to add a Message from the admin pages I get the 'Message' instance needs to have a primary key value before this relationship can be used.
Here is my models.py Message class...
class Message(models.Model):
""" Message as sent through a Submission. """
default_id = time.time()
#adding primary key to satisfy error
id = models.BigAutoField(primary_key=True, auto_created=True)
title = models.CharField(max_length=200, verbose_name=_('title'))
slug = models.SlugField(verbose_name=_('slug'))
newsletter = models.ForeignKey(
Newsletter, verbose_name=_('newsletter'), on_delete=models.CASCADE, default=get_default_newsletter
)
date_create = models.DateTimeField(
verbose_name=_('created'), auto_now_add=True, editable=False
)
date_modify = models.DateTimeField(
verbose_name=_('modified'), auto_now=True, editable=False
)
class Meta:
verbose_name = _('message')
verbose_name_plural = _('messages')
unique_together = ('slug', 'newsletter')
#order_with_respect_to = 'newsletter'
def __str__(self):
try:
return _("%(title)s in %(newsletter)s") % {
'title': self.title,
'newsletter': self.newsletter
}
except Newsletter.DoesNotExist:
logger.warning('No newsletter has been set for this message yet.')
return self.title
def get_next_article_sortorder(self):
""" Get next available sortorder for Article. """
next_order = self.articles.aggregate(
models.Max('sortorder')
)['sortorder__max']
if next_order:
return next_order + 10
else:
return 10
#cached_property
def _templates(self):
"""Return a (subject_template, text_template, html_template) tuple."""
return self.newsletter.get_templates('message')
#property
def subject_template(self):
return self._templates[0]
#property
def text_template(self):
return self._templates[1]
#property
def html_template(self):
return self._templates[2]
#classmethod
def get_default(cls):
try:
return cls.objects.order_by('-date_create').all()[0]
except IndexError:
return None
and the admin.py module
class MessageAdmin(NewsletterAdminLinkMixin, ExtendibleModelAdminMixin,
admin.ModelAdmin):
logger.critical('MessageAdmin')
save_as = True
list_display = (
'admin_title', 'admin_newsletter', 'admin_preview', 'date_create',
'date_modify'
)
list_filter = ('newsletter', )
date_hierarchy = 'date_create'
prepopulated_fields = {'slug': ('title',)}
inlines = [ArticleInline, AttachmentInline, ]
""" List extensions """
def admin_title(self, obj):
return format_html('{}', obj.id, obj.title)
admin_title.short_description = _('message')
def admin_preview(self, obj):
url = reverse('admin:' + self._view_name('preview'), args=(obj.id,),
current_app=self.admin_site.name)
return format_html('{}', url, _("Preview"))
admin_preview.short_description = ''
""" Views """
def preview(self, request, object_id):
logger.critical('preview object_id = %s', object_id)
return render(
request,
"admin/newsletter/message/preview.html",
{'message': self._getobj(request, object_id),
'attachments': Attachment.objects.filter(message_id=object_id)},
)
#xframe_options_sameorigin
def preview_html(self, request, object_id):
logger.critical('preview_html object_id = %s', object_id)
message = self._getobj(request, object_id)
if not message.html_template:
raise Http404(_(
'No HTML template associated with the newsletter this '
'message belongs to.'
))
c = {
'message': message,
'site': Site.objects.get_current(),
'newsletter': message.newsletter,
'date': now(),
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
return HttpResponse(message.html_template.render(c))
#xframe_options_sameorigin
def preview_text(self, request, object_id):
message = self._getobj(request, object_id)
c = {
'message': message,
'site': Site.objects.get_current(),
'newsletter': message.newsletter,
'date': now(),
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
return HttpResponse(
message.text_template.render(c),
content_type='text/plain'
)
def submit(self, request, object_id):
submission = Submission.from_message(self._getobj(request, object_id))
change_url = reverse(
'admin:newsletter_submission_change', args=[submission.id])
return HttpResponseRedirect(change_url)
def subscribers_json(self, request, object_id):
message = self._getobj(request, object_id)
json = serializers.serialize(
"json", message.newsletter.get_subscriptions(), fields=()
)
return HttpResponse(json, content_type='application/json')
""" URLs """
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('<object_id>/preview/',
self._wrap(self.preview),
name=self._view_name('preview')),
path('<object_id>/preview/html/',
self._wrap(self.preview_html),
name=self._view_name('preview_html')),
path('<object_id>/preview/text/',
self._wrap(self.preview_text),
name=self._view_name('preview_text')),
path('<object_id>/submit/',
self._wrap(self.submit),
name=self._view_name('submit')),
path('<object_id>/subscribers/json/',
self._wrap(self.subscribers_json),
name=self._view_name('subscribers_json')),
]
return my_urls + urls
I have tried a number of the fixes recommended in posts on this error here in stackoverflow and on github. but none have solved the problem.
I created the id = models.BigAutoField(primary_key=True, auto_created=True) and varified in the sqlite db that the primary key and auto increment are in the structure.
I feel like creating a through or xref table between Message and Article would solve it, but I hesitate because the developers of Jazzband Newsletter (https://github.com/jazzband/django-newsletter) did it this way for a reason and that Django or Python updates have let this creep in.
This is the problem: I have a serializer field pointing to another serializer. I sending an allright request to server and I get this response:
{
"purchase_header": [
"This field is required."
]
}
But the field was sended by POST request (in debugger I can see it).
I finded this code in html.py (from DRF):
def parse_html_dict(dictionary, prefix=''):
"""
Used to support dictionary values in HTML forms.
{
'profile.username': 'example',
'profile.email': 'example#example.com',
}
-->
{
'profile': {
'username': 'example',
'email': 'example#example.com'
}
}
"""
ret = MultiValueDict()
regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
for field in dictionary:
match = regex.match(field)
if not match:
continue
key = match.groups()[0]
value = dictionary.getlist(field)
ret.setlist(key, value)
return ret
Well, when debuging I can see that prefix variable contain "purchase_header" and field variable contain the same "purchase_header" string but de match fail returning None. Very rare... dictionary parameter contain all keys and values.
But If you can helpme I appreciate much.
This is the rest of significant code:
urls.py
router = DefaultRouter()
router.register(r'cancel-requests', PurchaseCancellationsRequestViewSet,
basename='purchase-cancel-requests')
router.register(r'invoices', PurchaseViewSet, basename='purchase')
urlpatterns = router.urls
urlpatterns += [path('payment-headers/', PaymentHeadersListAPIView.as_view(
), name='PaymentHeadersListAPIView')]
api.py
class PurchaseViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing purchases instances.
"""
serializer_class = PurchaseSerializer
queryset = Purchase.objects.all().order_by('-created_at')
permission_classes = (IsAuthenticated,)
serializer_action_classes = {
'list': PurchaseSerializer,
'create': PurchaseSerializer,
'retrieve': PurchaseSerializer,
'update': PurchaseSerializer,
'partial_update': PurchaseSerializer,
'destroy': PurchaseSerializer
}
def get_serializer_class(self):
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super().get_serializer_class()
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs["context"] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_list_queryset(self):
try:
store = Store.objects.get(my_owner=self.request.user)
except EmptyResultSet as e:
print("Data Error: {0}".format(e))
urlquery = self.request.GET.get('cancelled', '')
cancelled = False
if urlquery == 'true':
cancelled = True
elif urlquery == '':
# return all
return Purchase.objects.filter(created_for=store)
return Purchase.objects.filter(cancelled=cancelled, created_for=store)
def put(self, request, *args, **kwargs):
if request.user.is_seller:
raise PermissionDenied(
detail='Only owners can cancellate Purchases')
# seller create invoice cancellation request
return self.update(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
request.data['created_by'] = request.user.pk
print(request.data)
if request.user.is_owner is True:
try:
request.data['created_for'] = Store.objects.get(
my_owner=request.user).pk
except Exception as e:
print(e)
return Response(data='User has no Store associated',
status=status.HTTP_400_BAD_REQUEST)
else:
try:
request.data['created_for'] = request.user.seller_profile \
.my_store.pk
except Exception as e:
print(e)
return Response(data='User has no Store associated',
status=status.HTTP_400_BAD_REQUEST)
return self.create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
# TODO: date ranges
if self.request.user.is_seller:
# TODO: Change -> Indeed the seller can get his own created
# purchases and refactor this to method
raise PermissionDenied(
detail='Only owners can get purchase list.')
queryset = self.get_list_queryset()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
serializers.py
from rest_framework import serializers
from .models import Purchase, PurchaseCancellationRequest, PurchaseHeader
class PurchaseHeaderSerializer(serializers.ModelSerializer):
class Meta:
model = PurchaseHeader
fields = ['id', 'name']
extra = {
'id': {'read_only': True},
'name': {'read_only': True}
}
class PurchaseSerializer(serializers.ModelSerializer):
purchase_header = PurchaseHeaderSerializer()
class Meta:
model = Purchase
fields = ['id', 'cancelled', 'created_at', 'created_by',
'created_for', 'pic', 'total_amount', 'purchase_header']
extra = {
'cancelled': {'read_only': True},
'created_at': {'read_only': True},
'id': {'read_only': True}
}
def validate_total_amount(self, value):
"""
Sanitize the amount value.
:return: total_amount as float
"""
total_amount = None
try:
total_amount = float(value)
except Exception as e:
print(e)
if total_amount:
return value
else:
raise serializers.ValidationError('Exists an error with '
'total_amount value, probably '
'an empty param or bad float '
'format')
def validate_cancelled(self, value):
"""
Sanitize the cancelled value.
:return: cancelled as python boolean
"""
cancelled = None
try:
cancelled = False if value == 'false' else True
except Exception as e:
print(e)
if cancelled:
return value
else:
raise serializers.ValidationError('Exists an error with '
'cancelled value, probably an '
'empty param or bad boolean '
'value')
def create(self, validated_data):
payment_header_pk = validated_data.pop('purchase_header')
payment_header_obj = PaymentHeader.objects.get(pk=payment_header_pk)
purchase_obj = Purchase.objects.create(
payment_header=payment_header_obj, **validated_data)
return purchase_obj
class PurchaseCancellationRequestSerializer(serializers.ModelSerializer):
class Meta:
model = PurchaseCancellationRequest
fields = ['purchase']
models.py
class PurchaseHeader(SoftDeleteModel):
name = models.CharField(max_length=50)
city = models.ManyToManyField(City, through='purchases.ProviderCity')
created_at = models.DateTimeField(default=timezone.now(), editable=False)
modified_at = models.DateTimeField(default=timezone.now())
class ProviderCity(SoftDeleteModel):
city = models.ForeignKey(City, on_delete=models.PROTECT)
purchase_header = models.ForeignKey(PurchaseHeader,
on_delete=models.PROTECT)
class Meta:
unique_together = ('city', 'purchase_header')
Thank for all friends!!
I'm trying to create an API to upload multiple images per session, I'm using the Django REST Framework. This is the error that I get in Postman:
{
"images": {
"0": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got InMemoryUploadedFile."
]
}
}
}
models.py with 2 tables
class PostSession(models.Model):
class Meta:
verbose_name = 'session'
verbose_name_plural = 'sessions'
name = models.CharField(
max_length=255,
)
class PostImage(models.Model):
class Meta:
verbose_name = 'photo'
verbose_name_plural = 'photos'
name = models.ForeignKey(
to=PostSession,
on_delete=models.CASCADE,
null=True,
blank=True,
)
file = models.ImageField(
upload_to='photos',
)
serializers.py
class PostImageRetrieveSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = '__all__'
class PostImageUpdateSerializer(serializers.Serializer):
"""
This class for validate and save child items purpose
"""
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, )
file = serializers.ImageField(required=True, allow_null=False, allow_empty_file=False, )
def create(self, validated_data):
session_name = validated_data.get('name')
image_file = validated_data.get('file')
if session_name and isinstance(session_name, str):
session, _ = PostSession.objects.get_or_create(name=session_name, )
else:
session = None
instance = PostImage.objects.create(
name=session,
file=image_file,
)
return self.update(
instance=instance,
validated_data=validated_data,
)
def update(self, instance, validated_data):
instance.save()
return instance
class PostUploadSerializer(serializers.Serializer):
images = serializers.ListField(
child=PostImageUpdateSerializer(required=True, allow_null=False, many=False, ),
required=True,
allow_null=False,
allow_empty=False,
)
def validate(self, attrs):
images_list = attrs.get('images')
if not isinstance(images_list, list):
raise exceptions.ValidationError(detail={
'images': ['`images` field must be a list of dict object!', ],
})
return attrs
def save_many(self):
images_list = self.validated_data.get('images')
post_image_instances = []
for image_obj in images_list:
try:
post_image_serializer = PostImageSerializer(
context=self.context,
data=image_obj,
many=False,
)
post_image_serializer.is_valid(raise_exception=True, )
post_image = post_image_serializer.save()
post_image_instances.append(post_image)
except:
# TODO: Remove previous saved instances if needed (inside `post_image_instances`)?
pass
return post_image_instances
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
viewsets.py
class PostViewSet(viewsets.GenericViewSet):
parser_classes = [parsers.MultiPartParser, parsers.JSONParser, ]
serializer_class = PostImageRetrieveSerializer
queryset = PostImage.objects.all()
#action(methods=['POST', ], detail=False, serializer_class=PostUploadSerializer, )
def upload_images(self, request, *args, **kwargs):
upload_serializer = PostUploadSerializer(
context={'request': request, },
data=request.data,
many=False,
)
upload_serializer.is_valid(raise_exception=True, )
post_image_instances = upload_serializer.save_many()
serializer = self.get_serializer(
post_image_instances,
many=True,
)
return response.Response(
data=serializer.data,
status=status.HTTP_200_OK,
)
The Idea is that the API can create a session with multiple images.
https://gist.github.com/cokelopez/a98ee5569991b6555ecd216764c193ec
Django Rest Framework inserting and updating writable nested serializer
I trying to insert and update a writable nested serializer with Django Rest Framework, following examples like this. But it doesn't work.
What could I having doing wrong?
Controller
class JobCardViewSet(ModelViewSet):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = JobCardSerializer
http_method_names = ('get', 'post', 'put', 'patch')
def get_queryset(self):
if 'key' in self.request.GET:
key = self.request.GET['key']
return JobCard.objects.filter(models.Q(job_order_no__icontains=key) | models.Q(job_name__icontains=key))
if 'party' in self.request.GET:
return JobCard.objects.filter(party_name__name__icontains=self.request.GET['party'])
else:
return JobCard.objects.all()
def create(self, request, *args, **kwargs):
self.serializer_class = JobCardPostSerializer
return super(JobCardViewSet, self).create(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
self.serializer_class = JobCardPostSerializer
return super(JobCardViewSet, self).update(request, *args, **kwargs)
Serializer
My serializer
class PaperDescriptionPostSerializer(ModelSerializer):
class Meta:
model = PaperDescription
fields = '__all__'
class JobCardPostSerializer(ModelSerializer):
paper_description = PaperDescriptionPostSerializer(many=True)
class Meta:
model = JobCard
fields = '__all__'
def create(self, validated_data):
paper_description_list = validated_data.pop('paper_description')
job_card = JobCard.objects.create(**validated_data)
for paper in paper_description_list:
paper.update({"party_name": job_card.party_name})
paper.update({"job_order_no": job_card.job_order_no})
paper.update({"job_name": job_card.job_name})
paper.update({"job_date": job_card.job_date})
paper.update({"job_type": job_card.job_type})
job_card.paper_description.add(PaperDescription.objects.create(**paper))
return job_card
def update(self, instance, validated_data):
paper_description = validated_data.pop('paper_description')
instance.job_name = validated_data.get("job_name", instance.job_name)
instance.job_date = validated_data.get("job_date", instance.job_date)
instance.delivery_date = validated_data.get("delivery_date", instance.delivery_date)
instance.job_created_by = validated_data.get("job_created_by", instance.job_created_by)
instance.die_no = validated_data.get("die_no", instance.die_no)
instance.job_re_print = validated_data.get("job_re_print", instance.job_re_print)
instance.remarks = validated_data.get("remarks", instance.remarks)
instance.update_reason = validated_data.get("update_reason", instance.update_reason)
instance.job_details = validated_data.get("job_details", instance.job_details)
instance.party_name = validated_data.get("party_name", instance.party_name)
instance.reference_id = validated_data.get("reference_id", instance.reference_id)
instance.branch_name = validated_data.get("branch_name", instance.branch_name)
# instance.title = validated_data.get("title", instance.title)
instance.save()
keep_id = []
for choice in paper_description:
if "id" in choice.keys():
if PaperDescription.objects.filter(id=choice["id"]).exists():
c = PaperDescription.objects.get(id=choice["id"])
# c.text = choice.get('text', c.text)
c.job_name = choice.get('job_name', c.job_name)
c.job_date = choice.get('job_date', c.job_date)
c.job_element = choice.get('job_element', c.job_element)
c.thickness = choice.get('thickness', c.thickness)
c.no_of_pages = choice.get('no_of_pages', c.no_of_pages)
c.print_size = choice.get('print_size', c.print_size)
c.order_qty = choice.get('order_qty', c.order_qty)
c.no_of_ups = choice.get('no_of_ups', c.no_of_ups)
c.print_sheets = choice.get('print_sheets', c.print_sheets)
c.forms = choice.get('forms', c.forms)
c.no_of_plates = choice.get('no_of_plates', c.no_of_plates)
c.wastage_per_farma = choice.get('wastage_per_farma', c.wastage_per_farma)
c.front_color = choice.get('front_color', c.front_color)
c.double_single = choice.get('double_single', c.double_single)
c.total_wastage = choice.get('total_wastage', c.total_wastage)
c.paper_required = choice.get('paper_required', c.paper_required)
c.back_color = choice.get('back_color', c.back_color)
c.prints = choice.get('prints', c.prints)
c.ruling = choice.get('ruling', c.ruling)
c.party_name = choice.get('party_name', c.party_name)
c.paper_type = choice.get('paper_type', c.paper_type)
c.paper_size = choice.get('paper_size', c.paper_size)
c.paper_brand = choice.get('paper_brand', c.paper_brand)
c.machine_name = choice.get('machine_name', c.machine_name)
c.save()
keep_id.append(c.id)
else:
continue
else:
c = PaperDescription.objects.create(**choice)
keep_id.append(c.id)
for choice in instance.paper_description:
if choice.id not in keep_id:
choice.delete()
return instance
Error
On update
nested object with id already exists
{
"paper_description": [
{
"id": [
"paper description with this id already exists."
]
},
{
"id": [
"paper description with this id already exists."
]
}
]
}
I had the same problem. Just remove all validators from id field. It will help you.
class PaperDescriptionPostSerializer(ModelSerializer):
class Meta:
model = PaperDescription
fields = '__all__'
extra_kwargs = {
'id': {'validators': []},
}
I'm struggling to understand how class-based generic views work in Django. The documentation doesn't cover generic views in detail.
I've created a custom #list_route but there seems to be no way of calling it. Here's the code:
view:
class AnalyticsViewSet(viewsets.GenericViewSet):
queryset = models.BookingAnalytics.objects.exclude(status=booking_constants.BookingState.deleted)
permission_classes = [DRYPermissions, ]
filter_backends = [DjangoFilterBackend, ]
filter_class = filters.AnalyticsFilter
serializer_class = serializers.AnalyticsDetailSerializer
serializer_classes = {}
def list(self, request, *args, **kwargs):
if not request.user.is_role_admin:
raise exc.PermissionDenied()
queryset = self.get_queryset()
filtered_queryset = self.filter_queryset(queryset)
data = {
'stats': {
....
}
}
return response.Ok(data)
#list_route(methods=['POST'], url_path='export')
def export(self, request, *args, **kwargs):
queryset = self.get_queryset()
filtered_queryset = self.filter_queryset(queryset)
recipients = []
if 'recipients' in request.data:
recipients = request.data['recipients']
....
return response.NoContent()
model:
class BookingAnalytics(UUIDModel, TimeStampedModel):
...
class Meta:
verbose_name = _('booking analytic')
verbose_name_plural = _('booking analytics')
ordering = ('-booking_date', '-created',)
def __str__(self):
return self.uuid
#staticmethod
#authenticated_users
def has_read_permission(request) -> bool:
return request.user.is_role_admin or request.user.is_role_client
#staticmethod
#authenticated_users
def has_write_permission(request) -> bool:
return request.user.is_role_admin or request.user.is_role_client
#staticmethod
#authenticated_users
def has_object_list_permission(request) -> bool:
return request.user.is_role_admin
#authenticated_users
def has_object_export_permission(self, request) -> bool:
return request.user.is_role_admin
Here, the default list route works just fine. However, the export route isn't called at all.
What is it that I'm missing?
Contrary to that, I have another viewset with many custom routes, and they work perfectly:
class BookingViewSet(
MultipleSerializerMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet
):
lookup_field = 'uuid'
queryset = models.Booking.objects.all()
permission_classes = [DRYPermissions, ]
filter_backends = [filters.BookingFilterBackend, filters.BookingExportFilterBackend, DjangoFilterBackend, ]
filter_class = filters.BookingFilter
pagination_class = BookingViewSetPagination
serializer_class = serializers.BookingDetailSerializer
serializer_classes = {...}
....
#list_route(methods=['POST'], url_path='export-bookings')
def export_bookings(self, request, *args, **kwargs):
queryset = self.get_queryset()
filtered_queryset = self.filter_queryset(queryset)
recipients = []
if 'recipients' in request.data:
recipients = request.data['recipients']
....
return response.NoContent()
Here is how I end up making it work.
views.py
#api_view(['POST'])
#permission_classes([permissions.IsAuthenticated])
def export(request, format=None):
qs = models.BookingAnalytics.objects.all()
filtered_queryset = filters.AnalyticsFilter(request.GET, queryset=qs)
recipients = []
if 'recipients' in request.data:
recipients = request.data['recipients']
....
return response.NoContent()
urls.py
...
url(r'^export$', export, name='export'),
...