Django - combining form fields with modelform fields - django-forms

Using Django 1.8, I'm trying to render a form where some of the fields come from the database like the many-to-many field below and some other fields are not in the database at all:
models.py
class Transcript(models.Model):
name = models.CharField(max_length=65, unique=True)
forms.py
class SummaryForm(forms.Form):
size = forms.CharField(label='size', max_length=50, required=False)
transcripts = models.ManyToManyField(Transcript, blank=True)
class Meta:
widgets = {
'transcript': autocomplete.ModelSelect2Multiple(url='tr-autocomplete'),
}
Why on my template page I do not see transcripts rendered and how to render it?

This worked:
transcripts = forms.MultipleChoiceField(label='Transcript', required=False)

Related

Django Rest Framework - Updating a ForeignKey Field entry in the view

In my Django Rest Framework project, I have a ForeignKey relationship between two models:
class Book(models.Model):
...
category = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True, null=True)
...
class Category(models.Model):
name = models.CharField(max_length=100, blank=False)
As you can see, a Book can belong to a Category but it does not have to. That means the 'category' field could be null.
So, in my views.py, any Book instance can be updated/patched if the user wants to assign a certain Book to a particular Category. That views.py update method looks like this:
class UpdateBooksCategory(generics.GenericAPIView):
'''
Class-based view to update the 'category' field of a Book instance.
'''
serializer_class = BookSerializer
permission_classes = [IsAuthenticated]
def patch(self, request,*args, **kwargs):
# get the Book instance first
book = Book.objects.get(pk=request.data.get('bookId'))
# if it is not assigned to a Category, then assign it
if book and not book.category:
book.category = Category.objects.get(name=request.data.get('categoryName'))
book.save()
serializer = self.get_serializer(book, context={"request": request})
return Response(serializer.data)
# otherwise, return a generic response
return Response({'response': "You have already put the selected Book in a Category."})
If you can see, first I get the Book instance that the user wants to update by using the Book's ID. If its Category field is not already filled, I get a Category instance using the given category name and assign it.
For the sake of completeness, here are my serializer classes:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name']
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', /*some other fields*/,..., 'category']
So, finally my question: I wanted to know if this is the preferred way of updating a ForeingKey field like this? I mean looking at the UpdateBooksCategory class-based view, is this the right way of doing it? The code works ( I tested it with PostMan) but since I am new to DRF I wanted to know if such an updating process is correct.
You can change your BookSerializer:
class BookSerializer(serializers.ModelSerializer):
category_id = serializers.IntegerField(write_only=True)
category = CategorySerializer(read_only=True)
class Meta:
model = Book
fields = [
'id',
# some other fields,
'category',
'category_id',
]
category will be a nested data that is read only, then setting the category will be by including the category_id in your requests.

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.

Select one to many relation table in query set django

I would like to select one to many table relation in query set.I have two model.
class Post(models.Model):
postid = models.AutoField(primary_key=True)
class Meta:
db_table = 'post'
class Images(models.Model):
postid = models.ForeignKey(Post,on_delete=models.CASCADE)
image = models.ImageField(upload_to=upload_image_to, blank=True)
In list method of Post viewset, I would like to select format like=>
{
"postid": 1,
"images":[
image:"Image Result"
]
}
Here is Post viewset =>
def list(self, request):
queryset = self.queryset
page = self.paginate_queryset(queryset)
for post in self.queryset:
img = Images.objects.all().filter(postid=post.postid)
serializer = PostSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
How to insert this img result to post?
Take a look at the RelatedField documentation. Something like:
class PostSerializer(serializers.ModelSerializer):
images = serializers.StringRelatedField(many=True)
class Meta:
model = Post
fields = ['postid', 'images']
This will give you a string representation of the Image, but you can look at other Relationship fields in the documentation to customise the JSON representation of the image.
Doing it like this will likely mean you don't need to override list() in the viewset as well.

Multiple endpoints for a single model in REST framework

I have a REST framework app for a multi-page form:
class InformationRequest(models.Model):
# user information
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
# contact details
phone = models.CharField(max_length=60)
email = models.CharField(max_length=60)
I'm trying to create endpoints for each of the two blocks of data within the model:
UserInformationSerializer(serializers.Serializer):
first_name = serializers.CharField(max_length=60)
last_name = serializers.CharField(max_length=60)
ContactDetailsSerializer(serializers.Serializer):
phone = serializers.CharField(max_length=60)
email = serializers.CharField(max_length=60)
I'd like the endpoints to look like:
requests/1/user-informtion
requests/1/contact-details
But I'm unsure of how to structure the view to achieve this. Currently I'm using a model viewset:
class InformationRequestViewSet(viewsets.ModelViewSet):
queryset = InformationRequest.objects.all()
serializer_class = ??
Is it possible to have two serializers for one model?
It's certainly possible to have 2 (or any number of) serializers for a model. And you are on the right path. What you want is different urls mapping to different views. So in your case, it can be something like the following:
Note that I turned each of your serializers into a ModelSerializer.
path-to/serializers.py
class UserInformationSerializer(serializers.ModelSerializer):
class Meta:
model = InformationRequest
fields = ('first_name', 'last_name')
class ContactDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = InformationRequest
fields = ('phone', 'email')
Next, we have 2 different urls that point to 2 different views:
path-to/urls.py
urlpatterns = [
url(r'^requests/(?P<pk>\d+)/user-information/$', views.UserInformationDetail.as_view()),
url(r'^requests/(?P<pk>\d+)/contact-details/$', views.ContactInformationDetail.as_view()),
# ... other urls
]
And finally, the views themselves (I'm using generic RetrieveAPIView for convenience)
path-to/views.py
class UserInformationDetail(generics.RetrieveAPIView):
queryset = InformationRequest.objects.all()
serializer_class = UserInformationSerializer
class ContactInformationDetail(generics.RetrieveAPIView):
queryset = InformationRequest.objects.all()
serializer_class = ContactDetailsSerializer

Django Rest Framework - return reverse foreign key property?

I think this is simple and probably a duplicate, but I cannot figure it out by looking at the documentation.
I have Django models as follows:
class Image(models.Model):
manor = models.ForeignKey(Manor, related_name='image_for_manor')
filename = models.CharField(max_length=30, null=True, blank=True)
class Manor(models.Model):
id = models.IntegerField(primary_key=True)
I want the user to be able to query the Manor and see the related Image. I'd like this JSON to be returned:
{
id: 572,
image: 'my/filepath.png'
}
This is my view:
#api_view(['GET'])
def manor(request, id):
mymanor = Manor.objects.get(id=id)
serializer = ManorSerializer(mymanor)
return JSONResponse(serializer.data)
And these are my serializers:
class ImageFilePathSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('filename',)
class ManorSerializer(serializers.ModelSerializer):
image = ImageFilePathSerializer(source="image_for_manor")
class Meta:
model = Manor
fields = ('id', 'image')
But this doesn't work: I get an empty dictionary for image. (Even if it weren't empty, I realise it wouldn't be right, because I don't want the image property to be a dictionary: I want it to be a string.)
How can I change this to be correct? I cannot work it out.
As Kevin suggested , why dont you use ImageField which will give you url where your image is uploaded. although if you dont want to do that , here are some changes you have to do to get the result format you wanted.(Following solution assumes that only one image will be for one minor)
views.py ( why dont you ImageFilePathSerializer instead of another one as it also contains all the data you wanted)
#api_view(['GET'])
def manor(request, id):
mymanor = Image.objects.get(manor__id=id)
serializer = ImageFilePathSerializer(mymanor)
return Response(serializer.data)
serializers.py (add Id with filename in ImageFilePathSerializer)
class ImageFilePathSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('id', 'filename',)
class ManorSerializer(serializers.ModelSerializer):
filename = serializers.ImageField(source="image_for_manor")
class Meta:
model = Manor
fields = ('id', 'filename')

Resources