I'm trying to save a through model which has the following attributes via Django-rest-framework
when sending a POST (I'm trying to create a new instance), I get the following error:
AttributeError at /api/organisation/provider/
'EnabledExternalProvider' object has no attribute 'create'
any ideas as to what i'm doing incorrectly?
the through model in question is:
class EnabledExternalProvider(models.Model):
provider = models.ForeignKey(ExternalProvider, related_name='associatedProvider')
organisation = models.ForeignKey(Organisation, related_name='associatedOrg')
enabled = models.BooleanField(default=True)
tenantIdentifier = models.CharField('Tenant identifer for organisation', max_length = 128, null=True, blank=True)
def __str__(self):
return self.provider + '-' + self.organisation
my view is:
class EnabledExternalProvider(mixins.RetrieveModelMixin, mixins.UpdateModelMixin,generics.GenericAPIView):
serializer_class = ConnectedServiceSerializer
def get_queryset(self):
return EnabledExternalProvider.objects.filter(organisation=self.request.user.organisation_id)
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
# make sure to catch 404's below
obj = queryset.get(organisation=self.request.user.organisation_id)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
and my serializer is:
class ConnectedServiceSerializer(serializers.ModelSerializer):
provider=ExternalProviderSerializer(read_only=True)
organisation=OrganisationDetailSerializer(read_only=True)
class Meta:
model = EnabledExternalProvider
fields = ( 'organisation', 'enabled', 'tenantIdentifier')
read_only_fields = ('organisation', 'provider')
I'm POSTing the following:
{"provider":"1","tenantIdentifier":"9f0e40fe-3d6d-4172-9015-4298684a9ad2","enabled":true}
Your view doesn't have that method because you haven't defined it, or inherited from a class that has it; your mixins provide retrieve and update, but not create.
You could add mixins.CreateModelMixin to the inheritance, but at this point you should really be using a ViewSet instead.
Related
I'm creating an app in which notifications model exists. That is why I need a behavior like this: when a person requests the notification page, the field is_read which is boolean turns from default FALSE to TRUE. The trouble is that there could be many of objects, so how to set to TRUE all of them?
Model:
class Notification(models.Model):
is_read = models.BooleanField(default=False)
notification_from = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="notiffrom")
notification_to = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="notifto")
View:
class UserNotificationView(ListModelMixin, GenericAPIView, CreateModelMixin):
serializer_class = NotificationSerializer
def get_queryset(self):
notification_to = self.kwargs["notification_to"]
return Notification.objects.filter(notification_to=notification_to)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
I know that there are bulk requests in Django but struggle with making them with DRF. Maybe there are some other ways?
You could add the update function in the get_queryset function.
class UserNotificationView(ListModelMixin, GenericAPIView, CreateModelMixin):
serializer_class = NotificationSerializer
def get_queryset(self):
notification_to = self.kwargs["notification_to"]
queryset = Notification.objects.filter(notification_to=notification_to)
queryset.update(is_read = True)
return queryset
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
I've tried everything I can find on the internet here, and nothing seems to work, so wondering if lots of the previous answers are for old versions. I'm on Django 2.2.9.
#models.py
class ParentModel(models.Model):
title = models.CharField()
class ChildModel(models.Model):
parent = models.ForeignKey(
ParentModel,
on_delete=models.CASCADE,
related_name='parent'
)
# admin.py
#admin.register(ParentModel)
class ParentModelAdmin(admin.ModelAdmin):
model = ParentModel
def get_queryset(self, request):
return ParentModel.objects.get_complete_queryset()
class ChildModelForm(forms.Form):
def __init__(self, u, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = ParentModel.objects.get_complete_queryset()
class Meta:
model = ChildModel
fields = '__all__'
#admin.register(ChildModel)
class ChildModelAdmin(admin.ModelAdmin):
model = ChildModel
form = ChildModelForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "parent":
kwargs["queryset"] = ParentModel.objects.get_complete_queryset()
return super().formfield_for_foreignkey(db_field, request, **kwargs)
I have a manager query called get_complete_queryset on ParentModel that returns a broader set of Parents than the default queryset.
The setup above allows me to go to my ChildModelAdmin and select the 'hidden' Parents from the dropdown, but when I try and save it gives me this error:
parent instance with id 2 does not exist.
There must be some queryset the form is using to save the model that isn't overridden, but I can't find what it is.
You can override get_form method like this:
def get_form(self, request, obj, **kwargs):
form = super(<YourModelAdmin>,self).get_form(request, obj, **kwargs)
form.base_fields['<you_field>'] = forms.ModelChoiceField(queryset=<your_queryset>)
return form
Overview: need to generate a codename value for a hidden field in order to send it to an external API (1) and after that include the generated codename field in database (2).
Issue: How can I send the generated codename (HiddenField) to database?
models.py
class Category(Model):
name = models.CharField(max_length=45)
class Animal(Model):
name = models.CharField(max_length=45)
codename = models.CharField(max_length=45)
categories = models.ManyToManyField(Category)
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = models.Category
fields = '__all__'
class AnimalSerializer(serializers.ModelSerializer):
codename = serializers.HiddenField(default="auto_replace_me")
class Meta:
model = models.Animal
fields = '__all__'
views.py
class CategoryViewSet(ModelViewSet):
queryset = models.Category.objects.all()
serializer_class = serializers.CategorySerializer
class AnimalViewSet(ModelViewSet, CreateModelMixin):
queryset = models.Animal.objects.all()
serializer_class = serializers.AnimalSerializer
def create(self, request, *args, **kwargs):
codename = generate_codename()
# (1) external API request (with codename included on request) and returns a boolean `result`
if result:
# will call create method from parent class BUT
# (2) it needs to save the codename in database
return super(AnimalViewSet, self).create(request, *args, **kwargs)
else:
return HttpResponseServerError()
def generate_codename():
return ''.join([random.choice(string.ascii_letters) for n in range(10)])
Following code should do the job:
class AnimalViewSet(ModelViewSet, CreateModelMixin):
queryset = models.Animal.objects.all()
serializer_class = serializers.AnimalSerializer
def perform_create(serializer, codename):
serializer.save(codename=codename)
def create_with_codename(self, request, codename, *args, **kwargs):
# this is almost the same as CreateModelMixin 'create'
# but with one change: passing codename to 'perform_create' method
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer, codename)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def create(self, request, *args, **kwargs):
codename = generate_codename()
# (1) external API request (with codename included on request) and returns a boolean `result`
if result:
# will call create method from parent class BUT
# (2) it needs to save the codename in database
return self.create_with_codename(request, codename, *args, **kwargs)
else:
return HttpResponseServerError()
So basically, you should write your own function create_with_codename to change default behaviour of CreateModelMixin def create(self, request, *args, **kwargs)
Most important thing was to pass codename argument to perform_create. According to docs, everytime when you need to save serializer with appending additional values to it, you should use serializer.save(extra_field=extra_value)
After learn from you guys, I got this solution (only changing views.py):
views.py
class CategoryViewSet(ModelViewSet):
queryset = models.Category.objects.all()
serializer_class = serializers.CategorySerializer
class AnimalViewSet(ModelViewSet, CreateModelMixin):
queryset = models.Animal.objects.all()
serializer_class = serializers.AnimalSerializer
codename = None
def perform_create(self, serializer):
serializer.save(codename=self.codename)
def create(self, request, *args, **kwargs):
self.codename = generate_codename()
# (1) external API request (with codename included on request) and returns a boolean `result`
if result:
# will call create method from parent class BUT
# (2) it needs to save the codename in database
return super(AnimalViewSet, self).create(request, *args, **kwargs)
else:
return HttpResponseServerError()
def generate_codename():
return ''.join([random.choice(string.ascii_letters) for n in range(10)])
class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = EventInfo
fields = ('email', 'pin')
class EventSerializer(DataSerializer, GeoModelAPIView):
# other fields
event_info = InfoSerializer(read_only=True)
def create(self, validated_data):
event_info = validated_data.pop('event_info', {})
event = super().create(validated_data)
EventInfo.objects.create(event=event, **event_info)
return event
Model
class EventInfo(models.Model):
pin = models.CharField(max_length=60, null=False, blank=False)
email = models.EmailField()
event = models.ForeignKey(Event)
POST
{
# other data
"event_info": {
"email": "example#example.com",
"pin": "1234567890"
}
}
So I have a model that is not visible on the browsable API, but I want to be able to save data from POST request to that model. Using this code I can create the objects and it correctly links the info to a correct Event model. However the email and pin fields won't get saved. What I have figured out is that the 'event_info' data from the POST is not visible on the validated_data.
The validation goes to the DataSerializer's validation method but I guess that I should somehow bypass the validation for just the 'event_info' data?
Edit:
class EventViewSet(BulkModelViewSet, JSONAPIViewSet):
queryset = Event.objects.filter(deleted=False)
queryset = queryset.select_related('location')
queryset = queryset.prefetch_related(list of related fields)
serializer_class = EventSerializer
filter_backends = (EventOrderingFilter, filters.DjangoFilterBackend)
filter_class = EventFilter
ordering_fields = (fields to order by)
ordering = ('-last_modified_time',)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
def get_serializer_context(self):
context = super(EventViewSet, self).get_serializer_context()
context.setdefault('skip_fields', set()).update(set([
'headline',
'secondary_headline']))
return context
#atomic
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
if isinstance(serializer.validated_data, list):
event_data_list = serializer.validated_data
else:
event_data_list = [serializer.validated_data]
super().perform_create(serializer)
I have a model like so:
class GiveAbsolute(serializers.Field):
def to_native(self,value):
# this where it give an error (self doesn't have request)
# what i want it to give full url
# like: http://www.blabla.com/othermodel/1
return reverse('link_to_othermodel',
args=[value],
request=self.request)
class SomethingSerializer(serializers.ModelSerializer):
# field with foreign key
othermodel = GiveAbsolute(source="othermodel.id")
class Meta:
model=Something
fields("fields1","othermodel")
is there a way to achieve this ?
thanks
From the source
The request object is an entry of the context dictionary. ie.
request = self.context.get('request')
In your case, just do:
self.request = self.context.get('request')
then build the url
self.request.build_absolute_uri(reverse('some_url_name'))
Based on the answer of mariodev, here is a reusable solution for Models ; I use it to provide URLs to service (see them as metheds) on django models.
Reusable components
serializers.py
class RequestAwareSerializer(serializers.ModelSerializer):
"""
A serializer which fields can access the request object.
"""
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(RequestAwareSerializer, self).__init__(*args, **kwargs)
class APIMethodField(serializers.Field):
""" To get the absolute URL of a method accessible via the API
"""
def __init__(self, url_action_name, *args, **kwargs):
self._url_name = url_action_name
super(APIMethodField, self).__init__(source='*', *args, **kwargs)
def to_native(self, obj):
"""
#param objid the ID of the object
#param method_url_name, the name of the url, as in urls.py
"""
return reverse_lazy(self._url_name, args=[obj.id],
request=self.parent.request)
views.py
class ChattyModelViewSet(ModelViewSet):
""" ModelViewSet which informs the serializer about the request
(abstract)
"""
def get_serializer(self, instance=None, data=None,
files=None, many=False, partial=False):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files, many=many,
partial=partial, context=context,
request=self.request)
Example use
urls.py
url(r'^v1/maildomain/(?P<maildomain_id>\d+)/check/$',
views.MailDomainDetail.as_view(), name='maildomain_dns_check')
serializers.py
class MailDomainSerializer(RequestAwareSerializer):
checkdns_url = APIMethodField(url_action_name='maildomain_dns_check')
class Meta:
model = MailDomain()
fields = ('name', 'checkdns_url')
views.py
class MailDomainView(ChattyModelViewSet):
model = MailDomain
serializer_class = MailDomainSerializer
The only thing in DRF, that has an access to request object is the view, so you need to figure out how to pass your request from view to serializer, for example in generic ListView you can use get_serializer.
Then, when you already have it in your serializer, you can use self.parent (which is a parent serializer) to capture it from the field itself:
class GiveAbsolute(serializers.Field):
def to_native(self,value):
return reverse('link_to_othermodel',
args=[value],
request=self.parent.request)
class SomethingSerializer(serializers.ModelSerializer):
# field with foreign key
othermodel = GiveAbsolute(source="othermodel.id")
class Meta:
model=Something
fields=("fields1","othermodel")
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(SomethingSerializer, self).__init__(*args, **kwargs)
class SomethingView(generics.ListAPIView):
model = Something
serializer_class = SomethingSerializer
def get_serializer(self, instance=None, data=None,
files=None, many=False, partial=False):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files, many=many,
partial=partial, context=context, request=self.request)