Related
I have created 2 models -
Tags and Startups. Startups has a tags field with a ManytoMany relationship with Tag.
Models.py file -
from django.db import models
from django_extensions.db.fields import AutoSlugField
from django.db.models import CharField, TextField, DateField, EmailField, ManyToManyField
class Tag(models.Model):
name = CharField(max_length=31, unique=True, default="tag-django")
slug = AutoSlugField(max_length=31, unique=True, populate_from=["name"])
def __str__(self):
return self.name
class Startup(models.Model):
name = CharField(max_length=31, db_index=True)
slug = AutoSlugField(max_length=31, unique=True, populate_from=["name"])
description = TextField()
date_founded = DateField(auto_now_add=True)
contact = EmailField()
tags = ManyToManyField(Tag, related_name="tags")
class Meta:
get_latest_by = ["date_founded"]
def __str__(self):
return self.name
My serializers.py file -
from rest_framework.serializers import HyperlinkedModelSerializer, PrimaryKeyRelatedField, ModelSerializer
from .models import Startup, Tag
class TagSerializer(HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = "__all__"
extra_kwargs = {
"url": {
"lookup_field": "slug",
"view_name": "tag-api-detail"
}
}
class StartupSerializer(HyperlinkedModelSerializer):
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Startup
fields = "__all__"
extra_kwargs = {
"url": {
"lookup_field": "slug",
"view_name": "startup-api-detail"
}
}
My viewsets.py file -
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from .serializers import TagSerializer, StartupSerializer
from .models import Tag, Startup
from rest_framework.decorators import action
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_204_NO_CONTENT
from django.shortcuts import get_object_or_404
class TagViewSet(ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
lookup_field = "slug"
class StartupViewSet(ModelViewSet):
serializer_class = StartupSerializer
queryset = Startup.objects.all()
lookup_field = "slug"
#action(detail=True, methods=["HEAD", "GET", "POST"], url_path="tags")
def tags(self, request, slug=None):
startup = self.get_object()
print(startup)
if request.method in ("HEAD", "GET"):
s_tag = TagSerializer(
startup.tags,
many=True,
context={"request": request}
)
return Response(s_tag.data)
tag_slug = request.data.get("slug")
if not tag_slug:
return Response(
"Slug of Tag must be specified",
status=HTTP_400_BAD_REQUEST
)
tag = get_object_or_404(Tag, slug__iexact=tag_slug)
startup.tags.add(tag)
return Response(HTTP_204_NO_CONTENT)
I can create startups and relate tags through my django admin.
I have a dropdown list with all the created tags in my django admin from where I can relate tags with startups.
I do not understand how can I relate tags with startup when creating startup in react.
I tried posting data in this form -
{
"name": "Test Startup 1",
"description": "First Desc",
"contact": "first#gmail.com",
"tags": [
{
"url": "http://127.0.0.1:8000/api/v1/tag/first-tag/",
"name": "First Tag",
"slug": "first-tag"
}
]
}
I cannot get the tags to relate with startup.
How to handle related fields?
You have multiple options.
If you want to perform association (meaning tags and startup already exists before making the request, kinda what happen with django-admin), you could create a new serializer that have a different field for tags, accepting ids instead of the nested serializer.
If you want to have nested creation/edition, you could checkout WritableNestedSerializer from here. Because the doc says that it does not handle such usecase, because they might be many way to perform this depending on your business logic, but provide ways to perform that yourself here
Another approach would be to have a route with nested ressources (with nested routers for instance) so when you POST a tag in /startup/1/tags/ you create AND associate your tag automatically, like you did.
Now, concerning your endpoint, you need to get the data of your request and pass it to the tag serializer. This serializer will then validate your data, and if it is valid, you can perform tag creation.
To do that, you can do something like:
tag_data = request.data
tag_serializer = TagSerializer(data=request.data)
tag_serializer.is_valid()
tag = tag_serializer.save()
tag.startup_set.add(startup)
Adding the relationship have to be done in two step. You should use a transaction to ensure it is correctly created.
Also, instead of adding this logic in your view, you should override your TagSerializer/StartupSerializer create method to do that.
I want to know how i can upload multiple image (list) in my views and get a Response like this :
{
"id":1,
"title":"text",
"roms":2,
"image":[
"http:localhost:8000/media/images/houses/image1.png",
"http:localhost:8000/media/images/houses/image2.png",
"http:localhost:8000/media/images/houses/image3.png",
]
}
I have tried this way :
models.py
from django.db import models
class House(models.Model):
title = models.CharField(max_length=255)
rooms = models.IntegerField()
images = models.FileField(upload_to="images/houses/")
def __str__(self):
return self.title
serializers.py*
from rest_framework import serializers
from .models import House
class HouseSerializer(serializers.ModelSerializer):
image = serializers.ListField(max_length=None,child=serializers.FileField)
class Meta:
model = House
fields = '__all__'
views.py
from rest_framework import viewsets
from .models import House
from .serializers import HouseSerializer
class HomeViewSet(viewsets.ModelViewSet):
queryset = House.objects.all()
serialiezer_class = HouseSerializer
But it didn't work, i don't know how to write serializer and views to do this, any body help please.
Thanks in advance
Upload multi img to 1 post on Django ? How do that?
First of all you have to change your models. Then you need to add to this model by turning on the photos loaded with the for loop to your image model. I do not know exactly how to do this event with the rest api, but this is how it is done under normal conditions.
So I am relatively new to Django, and DRF. I recently started a project to learn more about the platform.
I also found best practice guide line which I am following at the moment. Here is the Django Style Guide
The guild line says that every App should completely decouple itself and use UUID instead of foreign key,
And every business rule should go into services.py (Which I like btw.)
Here comes the problem. I am not sure how I can construct two models together to produce a nested JSON Output.
Ex: I have Post() model and Comments() model. and Comment model uses uuid of Post model as a referential id.
Now in my API.py, I am not sure how I can join these two together in the Serializer.py
FYI the code below is only for demo purposes, may not be executable
Model
class Post(models.Model):
user_id = models.UUIDField(default=uuid.uuid4)
title = models.CharField(max_length=100)
description = models.CharField(max_length=100)
pictures = models.CharField(max_length=100)
lat = models.CharField(max_length=16)
long = models.CharField(max_length=16)
vote = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
post_id = models.UUIDField(default=uuid.uuid4)
parent_id = models.ForeignKey("self", on_delete=models.CASCADE)
text = models.CharField(max_length=1000)
up_vote = models.IntegerField()
down_vote = models.IntegerField()
user_id = models.UUIDField(default=uuid.uuid4)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
Service
def get_report(id) -> Dict:
logger.info('Get an Report by Id')
post= Post.objects.get(id=id)
return {
'post'= post
}
def get_comments(id) -> Dict:
logger.info('Get an Report by Id')
comments = Comment.objects.filter(post_id=id)
return {
'comments' = comments
}
API
class ReportGetApi(APIView):
class OutputSerializer(serializers.ModelSerializer):
comments = CommentsSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')
class CommentsSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')
def get(self, request):
post = PostService.get_post() #Only one item
comments = PostService.get_comments() #Many Items
serializer = self.OutputSerializer(post, many=True)
return Response(serializer.data)
You actually don't need a services file to do what you're asking. DRF can handle what you want directly with serializers and using a RetrieveAPIView.
So using your models you have above you could have something like the following:
serializers.py
from rest_framework import serializers
from . import models
class CommentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.Comment
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
comments = serializers.SerializerMethodField()
class Meta:
model = models.Post
fields = '__all__'
def get_comments(self, obj):
comments = models.Comment.objects.filter(post_id=obj.id)
return CommentSerializer(comments, many=True).data
views.py
from rest_framework import generics
from . import serializers, models
class ReportApi(generics.RetrieveAPIView):
serializer_class = serializers.PostSerializer
queryset = models.Post.objects.all()
You would need to specify the URL to pass in the primary key of the Post object you want to retrieve like so:
urls.py
from django.urls import path
from api import views
app_name = 'api'
urlpatterns = [
path('get_post_report/<pk>/', views.ReportApi.as_view()),
]
Then you would access the view using something like http://example.com/api/get_post_report/12345678/.
Note: You must configure the urls.py within your project's
urls file to use 'api/' for including your app's urls for the
'/api/' part of the url above to be a part of the url.
If you don't know how to set up urls refer to the Django Tutorial
This will then give you something like the following:
response.json
{
"id": 1,
"comments": [
{
"id": 1,
"post_id": "00000000-0000-0000-0000-000000000001",
"text": "Comment Text",
"up_vote": 0,
"down_vote": 0,
"user_id": "00000000-0000-0000-0000-000000000001",
"created_at": "2020-05-29T13:14:07.103072Z",
"modified_at": "2020-05-29T13:14:07.103124Z",
"parent_id": null
}
],
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "My Post",
"description": "Post Description",
"pictures": "No Pictures",
"lat": "12",
"long": "12",
"vote": "12",
"created_at": "2020-05-29T13:14:07.102316Z",
"modified_at": "2020-05-29T13:14:07.102356Z"
}
Lastly
I looked at the guide you are referring to and did not see a reference to decouple models with UUID's. While you can do that (and in some cases may be necessary) I would think hard on whether you truly need that much decoupling.
There are reasons to use foreign keys over UUID's such as accessing the related models easier and faster. Decoupling using UUID's means you will need to write more boilerplate code every time you need to access a related model.
It comes down to deciding whether you need decoupling or a better developer experience with (subjectively) cleaner code. Please don't just follow a guide and assume it's law. More experience will help with this.
For example, if you used a foreign key relationship your PostSerializer could look like the following:
example_serializers.py
class PostSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
comments = CommentSerializer(many=True)
class Meta:
model = models.Post
fields = '__all__'
See how we got rid of the get_comments(self, obj) method? That was just one bit of boilerplate that we got rid of by making the design decision to use foreign key relationships. Now just imagine a codebase of millions of lines of code and many serializers. Remember, the more code you write the more testing/debugging you need to do as well.
Again, just my opinion, but be sure you actually need to decouple your models before you do it.
Also, I strongly recommend you follow the DRF tutorial. It reviews everything you need to accomplish what I just posted here.
Hope this helps!
You could create one serializer called OutputSerializer and include the comments as its own field? if you change comments = OutputCommentSerializer to comments = CommentsSerializer(many=True, read_only=True) I believe that will depict what you want...(example below). You already defined CommentSerializer, thus you can use it.
class ReportGetApi(APIView):
class OutputSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')
class CommentsSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')
def get(self, request):
post = PostService.get_post() #Only one item
comments = PostService.get_comments() #Many Items
serializer = self.OutputSerializer(post, many=True)
return Response(serializer.data)
Django and MongoDB
Supporting a different set of fields for each document in a collection is one of MongoDB's features. It allows you to store similar data, but with different properties in the same collection.
for example:
{
_id: ObjectId("51156a1e056d6f966f268f81"),
type: "Article",
author: "Derick Rethans",
title: "Introduction to Document Databases with MongoDB",
date: ISODate("2013-04-24T16:26:31.911Z"),
body: "This arti…"
},
{
_id: ObjectId("51156a1e056d6f966f268f82"),
type: "Book",
author: "Derick Rethans",
title: "php|architect's Guide to Date and Time Programming with PHP",
isbn: "978-0-9738621-5-7"
}
Django dose not support Non-Relational data base like mongodb by default, but there are some lib's for this purpose. for example Django MongoDB Engine is a MongoDB backend for Django.
MongoDB allow to use different set of fields for each document in a collection, but in django you have to define models.py:
from django.db import models
from djangotoolbox.fields import ListField
class Post(models.Model):
title = models.CharField()
text = models.TextField()
tags = ListField()
comments = ListField()
the Question is: is there any way to define different set of fields for each document in a collection in MongoDB, when using Django ?
The Alternative
I like using django-mongoengine as it makes things clearer when dealing with MongoDB models.
For example, you can create structured Documents that are going to be transformed into models or EmbeddedDocument`s that are structured documents to be used in an already existed model.
from django_mongoengine import Document, EmbeddedDocument, fields
class Comment(EmbeddedDocument):
created_at = fields.DateTimeField(
default=datetime.datetime.now, editable=False,
)
author = fields.StringField(verbose_name="Name", max_length=255)
email = fields.EmailField(verbose_name="Email")
body = fields.StringField(verbose_name="Comment")
class Post(Document):
created_at = fields.DateTimeField(
default=datetime.datetime.now, editable=False,
)
title = fields.StringField(max_length=255)
slug = fields.StringField(max_length=255, primary_key=True)
comments = fields.ListField(
fields.EmbeddedDocumentField('Comment'), blank=True,
)
The Answer
So for your case what you need to use is Dynamic document schemas that work in the same way as Document but any data/attributes set to them will also be saved.
class Page(DynamicDocument):
title = StringField(max_length=200, required=True)
# Create a new page and add tags
>>> page = Page(title='Using MongoEngine')
>>> page.tags = ['mongodb', 'mongoengine']
>>> page.save()
>>> Page.objects(tags='mongoengine').count()
>>> 1
I struggled this problem in user profile page. This is my solution.
model.py
from django.contrib.auth.models import User
from django.db import models
from django.core.validators import RegexValidator
# Create your models here.
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,primary_key=True,related_name="user_profile")
fullname = models.CharField(max_length=100,verbose_name="full name")
about = models.CharField(max_length=300,blank=True,null=True)
hobies = models.CharField(max_length=200,blank=True)
recent_aktivity = models.CharField(max_length=150,verbose_name="recent activity",null=True)
photo = models.ImageField(blank=True,null=True,upload_to="images/")
recent_badges = models.CharField(max_length=100,verbose_name="recent badges",null=True)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
phone_number = models.CharField(validators=[phone_regex], max_length=17, blank=True,null=True,verbose_name="phone number")
website_url = models.URLField(blank=True,null=True,verbose_name="company website")
projects =models.CharField(max_length=200,blank=True,null=True)
bio = models.CharField(max_length=300,blank=True,null=True)
def __str__(self):
return f'{self.user.username}-ProfileModel'
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Profile
#receiver(post_save,sender=User)
def update_user_profile(sender,instance,created,**kwargs):
if created:
profile = Profile.objects.create(user =instance)
app.py
from django.apps import AppConfig
class ProfileConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'Profile'
def ready(self):
import Profile.signals
forms.py
from django import forms
from.models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = '__all__'
exclude = ['user']
views.py
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.urls.base import reverse
from .forms import ProfileForm
from .models import Profile
from django.shortcuts import redirect, render,get_object_or_404
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
login_required(login_url="user:login")
def dashboard(request):
return render(request,"dashboard.html")
#login_required(login_url="user:login")
def get_profile(request):
profile = get_object_or_404(Profile,user=request.user)
return render(request,"profile.html",{"profile":profile})
I've been looking and following the Django-Rest Official docs here and tried to base my approach on that, except I was using a OneToOne relationship.
However, when I go to add in the browsable interface, it doesn't pick up the value in the nested text fields, and tells me that it can't be null. I've tried Googling and searching on here, but can't really find anything that works for getting a OneToOne relationship to work like what I want. I'm new to the REST-framework, and am just really confused. Thanks!
Basically, each verb needs to have one past tense object, which has those three fields (for testing, more, or even another layer of nesting, will be added later). I just can't get them to add with the browsable API.
Models.py:
from django.db import models
class Verb(models.Model):
verb = models.TextField()
verbal_noun = models.TextField()
verbal_adjective = models.TextField()
present = models.TextField()
future = models.TextField()
habitual_present = models.TextField()
conditional = models.TextField()
past_habitual = models.TextField()
past_subjunctive = models.TextField()
present_subjunctive = models.TextField()
imperative = models.TextField()
class Past(models.Model):
verb = models.OneToOneField(Verb)
first_singular = models.TextField()
second_singular = models.TextField()
third_singular = models.TextField()
Serializers.py:
from rest_framework import serializers
from conjugations.models import Verb, Past
class PastSerializer(serializers.ModelSerializer):
class Meta:
model = Past
fields = ('first_singular','second_singular','third_singular')
class VerbSerializer(serializers.ModelSerializer):
past = PastSerializer()
class Meta:
model = Verb
fields = ('verb','verbal_noun','verbal_adjective','past','present',
'future','habitual_present','conditional','past_habitual',
'past_subjunctive','present_subjunctive','imperative' )
def create(self, validated_data):
past_data = validated_data.pop('past')
verb = Verb.objects.create(**validated_data)
for past in past_data:
Past.objects.create(verb=verb, **past)
return verb
Raw data input:
{
"verb": "test",
"verbal_noun": "test",
"verbal_adjective": "test",
"past": {
"first_singular": "test1",
"second_singular": "test2",
"third_singular": "test3"
},
"present": "test",
"future": "test",
"habitual_present": "test",
"conditional": "test",
"past_habitual": "test",
"past_subjunctive": "test",
"present_subjunctive": "test",
"imperative": "test"
}
Views.py
from conjugations.models import Verb
from conjugations.serializers import VerbSerializer
from rest_framework import generics, permissions
class VerbList(generics.ListCreateAPIView):
queryset = Verb.objects.all()
serializer_class = VerbSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class VerbDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Verb.objects.all()
serializer_class = VerbSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from conjugations import views
urlpatterns = [
url(r'^verbs/$', views.VerbList.as_view()),
url(r'^verbs/(?P<pk>[0-9]+)/$', views.VerbDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Error:
File "/home/Projects/Python/virtualenvs/remnigh/lib/python3.5/site-packages/rest_framework/mixins.py", line 26, in perform_create
serializer.save()
File "/home/Projects/Python/virtualenvs/remnigh/lib/python3.5/site-packages/rest_framework/serializers.py", line 191, in save
self.instance = self.create(validated_data)
File "/home/Projects/Python/reimnigh-test/reimnigh/conjugations/serializers.py", line 19, in create
past_data = validated_data.pop('past')
KeyError: 'past'
My serializers.py was messed up a little. I needed to remove the for loop for past_data since it was a one-to-one and just map that directly. Corrected file below:
from rest_framework import serializers
from conjugations.models import Verb, Past
class PastSerializer(serializers.ModelSerializer):
class Meta:
model = Past
fields = ('first_singular','second_singular','third_singular')
class VerbSerializer(serializers.ModelSerializer):
past = PastSerializer()
class Meta:
model = Verb
fields = ('verb','verbal_noun','verbal_adjective','past','present',
'future','habitual_present','conditional','past_habitual',
'past_subjunctive','present_subjunctive','imperative')
def create(self, validated_data):
past_data = validated_data.pop('past')
verb = Verb.objects.create(**validated_data)
Past.objects.create(verb=verb, **past_data)
return verb
It also helps to make sure your migrations are up to date too. That might have had a little bit to do with it.
Make sure your request is performed as json content type. HTML form don't support nested serializers.
Try changing your past field in the VerbSerializer to this:
past = PastSerializer(source='past_set')
This is syntax you could use if the Past-Verb relationship was a foreign key- I can't say for sure if it will work for you, but the DRF documentation says that for reverse relationships to be nested, a related name must be specified, either as above or in the model definition.
If this doesn't work, could you post the exact error/traceback?