HyperlinkedRelatedField temporarily as read_only - django-rest-framework

I have field declared in the following way:
field = serializers.HyperlinkedRelatedField(
view_name='field-detail',
source='feature',
queryset=Field.objects.all()
)
Do you know how I can temporarily set such field as read_only ?
Unfortunately such construction doesn't work :(
serializer.Meta.extra_kwargs = {
'field': {'queryset': None, 'read_only': True}
}
it works fine, when field is declared as a ForeignKey in the Model e.g.
class Foo(models.Model):
field = models.ForeignKey(...)
class FooSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Foo
fields = ('url', 'field',...)
and then (as I mention above), or even:
serializer.Meta.read_only_fields = ('field',)

Try to pass read_only property while declaring the field when you are using HyperlinkedRelatedField
field = serializers.HyperlinkedRelatedField(
view_name='field-detail',
lookup_field='feature',
# set read_only to True
read_only=True
)
Read docs: hyperlinkedmodelserializer

Related

Making a M2M relation required in DRF

My goal is to make an M2M field required in DRF serializer. Example:
class ModelA:
b = ManyToManyField(B)
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = ('b')
Here I want to make 'b' a required field for ModelASerializer, so that when creating an instance of A class, at least one relation is added to the newly created instance. How can this be achieved ?
you can use PrimaryKeyRelatedField with many=True attribute for that purpose. For example;
from rest_framework.serializers import PrimaryKeyRelatedField
class ModelASerializer(serializers.ModelSerializer):
b = PrimaryKeyRelatedField(queryset=B.objects.all(), many=True)
class Meta:
model = ModelA
fields = ('b')
and if you don't send b values then you will get required field error like;
{
"b": [
"This field is required."
]
}
also, if you want to block empty list then just use allow_empty=False attribute on serializer field.

Return dictionary instead of list of dictionaries

I have a model which contain two fields. One field I want to be the key and second the content
I want my Modelserializer to return a dictionary that contain column1 value as a key and column2 value like value:
{
"column1Value": "column2Value",
"column2Value": "column2Value",
"column3Value": "column3Value"
}
right now my serializer is returning something like this
[
{
"columnField1Name": "field1",
"columnField2Name": "field2",
}
....
]
Thanks!
model.py
class Test(models.Model):
code = models.SlugField(max_length=100)
content = models.TextField()
serializers.py
class TestSerializer(ModelSerializer):
class Meta:
model = Test
fields = ['code', 'content']
This is how I would do it not sure if it is best suited for your use case.
Add a SerializerMethodField() in your serializer class and in the method definition create a dictionary for each model instance.
class TestSerializer(ModelSerializer):
field_name = serializers.SerializerMethodField()
class Meta:
model = Test
fields = ['field_name']
def get_field_name(self, obj):
column1value = obj.column1
column2value = obj.column2
return {
str(column1value) : str(column2value)
}
However, field_name from serialized data will be a dictionary. To turn it into your expected formate you can merge each dictionary of list into an empty dictionary.
some_dict = {}
for dicti in field_name:
some_dict.update(dicti)
You can do this in your views.py.

How can I change HyperLinkedModelSerializer's default <pk> lookup_url_kwarg?

I want to use HyperLinkedModelSerializer in order to add a url field for my Book model. Here is the solution you'd typically find in the average tutorial:
# serializers.py
class BookSerializer(HyperLinkedModelSerializer):
class Meta:
model = Book
fields = ("title", "url",)
# views.py
class BookView(RetrieveAPIView):
serializer_class = BookSerializer
# urls.py
urlpatterns = [
path("<pk>/", BookDetailView.as_view(), name="book-detail"),
]
And that works all right. But now I need to change the URL conf in order to match the book id, not with <pk>, but with <fiction_id>. So I figured I'd just change it!
# urls.py
urlpatterns = [
path("<fiction_id>/", BookDetailView.as_view(), name="book-detail"),
]
Now comes the crash:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "fiction-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
I've tried fiddling with the lookup_field, lookup_url_kwargs in my view:
# views.py
class BookView(RetrieveAPIView):
serializer_class = BookSerializer
lookup_field = "pk"
lookup_url_kwargs = "fiction_id"
I've tried reminding the serializer to actually look for the (default) "pk":
# serializers.py
class BookSerializer(HyperLinkedModelSerializer):
class Meta:
model = Book
fields = ("title", "url",)
extra_kwargs = {
"url": {"lookup_field": "pk"},
}
I've tried combinations of these, to no avail. It looks like you can't use anything but <pk> if you want to take advantage of HyperLinkedModelSerializer's url field. The documentation doesn't seem to offer a way to change that behaviour:
By default hyperlinks are expected to correspond to a view name that matches the style '{model_name}-detail', and looks up the instance by a pk keyword argument.
How can I change this behaviour, or is it bound to become too messy?
You should check HyperlinkedModelSerializer implementation and see that it uses a serializer_related_field defaulting to HyperlinkedRelatedField
class HyperlinkedModelSerializer(ModelSerializer):
"""
A type of `ModelSerializer` that uses hyperlinked relationships instead
of primary key relationships. Specifically:
* A 'url' field is included instead of the 'id' field.
* Relationships to other instances are hyperlinks, instead of primary keys.
"""
serializer_related_field = HyperlinkedRelatedField
...
And then HyperlinkedRelatedField has a class attribute lookup_field defaulting to pk
class HyperlinkedRelatedField(RelatedField):
lookup_field = 'pk'
...
What you can do is to use a custom HyperlinkedRelatedField with your own lookup_field
from rest_framework.relations import HyperlinkedRelatedField
from rest_framework.serializers import HyperlinkedModelSerializer
class BookHyperlinkedRelatedField(HyperlinkedRelatedField):
lookup_field = 'fiction_id'
class BookSerializer(HyperLinkedModelSerializer):
serializer_related_field = BookHyperlinkedRelatedField
class Meta:
model = Book
fields = ("title", "url",)
In order to do this, you need to give the url field's new name for the lookup in the matched pattern by passing it through the extra_kwargs dictionary:
# serializers.py
class BookSerializer(HyperLinkedModelSerializer):
class Meta:
model = Book
fields = ("title", "url",)
extra_kwargs = {
"url": {"lookup_url_kwarg": "fiction_id"},
}
Also remember to modify the corresponding view:
# views.py
class BookView(RetrieveAPIView):
serializer_class = BookSerializer
lookup_url_kwarg = "fiction_id"
Do not write lookup_url_kwargs in plural.
You don't need to meddle with the lookup_field at any level as long as the lookup will be done on the model's primary key.

How to use index in search API in elasticsearch-dsl(5.4.0)

I'm using elasticsearch-dsl(5.4.0) and elasticsearch(5.5.3). I defined EsTask class with an inner class named Meta class as follows in model layer file:
/task_models.py
class EsTask(DocType):
id = Keyword()
catagory_id = Integer()
name = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
priority_level = Integer()
related_id = Keyword()
parent_id = Keyword()
creator_id = Keyword()
created_at = Date()
deleted_at = Date()
class Meta:
index = 'task_es'
doc_type = 'main'
I call search API in controller layer file:
/task.py
s = EsTask.search().filter('bool', must_not=elasticsearch_dsl.Q('exists', field='deleted_at'))
May I use keyword argument calling search() like search(index='task_es', doc_type='main') as expected if I want to ensure the search API use the indexes I defined in Meta Class above? Or that is necessarily and I can just leave search API without any argument?
You can leave it without any arguments as it will do it automatically.
Hope this helps!

Django admin form extra field

I want to add an extra field (column) that is not in the model. So, I created a model form in which the new extra field is defined.
admin.py
class WorkingHourForm(forms.ModelForm):
extra_field = forms.CharField(max_length=100)
class Meta:
model = WorkingHour
fields = ['day', 'period', 'time_range', 'extra_field']
class WorkingHourInline(admin.TabularInline):
model = WorkingHour
form = WorkingHourForm
This should work because it's pretty much a copy of an example in the documentation.
However, this raises the error: Unable to lookup 'extra_field' on WorkingHour or WorkingHourInline
What did I do wrong?
class WorkingHourForm(forms.ModelForm):
extra_field = forms.CharField(label='extra_field', max_length=100)
class Meta:
model = WorkingHour
fields = ['day', 'period', 'time_range', 'extra_field']
class WorkingHourInline(admin.TabularInline):
model = WorkingHour
form = WorkingHourForm
Adding label='extra_field' solved this for me in a similar use of tabularinline.. I think the django admin example works as is but when used in conjunction with the admin.TabularInline, it does not. Hope that helps.
remove extra_field from fields = ['day', 'period', 'time_range', 'extra_field']. If you do like that django will tries to get value for the extra_field from model. so, it will raises an error.
After modification, Above code will look like
class WorkingHourForm(forms.ModelForm):
extra_field = forms.CharField(max_length=100)
class Meta:
model = WorkingHour
fields = ['day', 'period', 'time_range']
class WorkingHourInline(admin.TabularInline):
model = WorkingHour
form = WorkingHourForm
Try adding this in your form's Meta:
labels = {"extra_field": "blah"}

Resources