I know how to render the form of serializers as a whole but I do not know how to render it individually similar to django forms on a template like
{{ form.name }}
{{ form.name.errors }}
My current files and codes are the following:
models.py
# Create your models here.
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
serializers.py
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
form.html
{% extends 'main/base.html' %}
{% load rest_framework %}
{% block content %}
<div class="container">
<form method="POST" id="id-login-form" class="panel-body">
{% csrf_token %}
{% render_form serializer %}
<!-- {% render_form serializer template_pack='rest_framework/horizontal' %}
{% render_form serializer template_pack='rest_framework/vertical' %} -->
<!-- {{ serializer }} -->
<input type="submit" value="Save">
</form>
</div>
{% endblock %}
DRF provides another template tag, in addition to {% render_form %} called {% render_field %} that's actually used within the former to render all the fields.
So your views.py would look something like this (you need to send this style dict in your response):
class SnippetCreate(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'snippets/snippet_create.html'
style = {'template_pack': 'rest_framework/vertical/'}
def get(self, request):
serializer = SnippetSerializer()
return Response({'serializer': serializer, 'style': self.style})
def post(self, request):
serializer = SnippetSerializer(data=request.data)
return Response({'serializer': serializer, 'style': self.style})
And in your template you could then just do:
{% render_field serializer.title style=style %}
{% render_field serializer.code style=style %}
Best way to understand what's going on is actually to look at the source itself:
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/templatetags/rest_framework.py#L35
which then calls
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/renderers.py#L325
Related
I used a form to create a django post with a single "text" field. Then I modified the post model. Now there are three forms "author", "body" and "title". Also changed the mentions in home.html and other files. Logically, it should work, but it gives an error in the file home.html
there is no such column: pages_posts.title
error in line 5
Some files from my project:
views.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views.generic import TemplateView, CreateView, DetailView
from django.views.generic import ListView
from .models import Posts
class HomePageView(ListView):
model = Posts
template_name = 'home.html'
# post_list = Posts.title
# context_object_name = 'all_posts_list'
class AboutPageView(TemplateView):
template_name = 'about.html'
class SignUpView(CreateView):
form_class = UserCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
class NewPostUpView(CreateView):
form_class = UserCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
class BlogDetailView(DetailView):
model = Posts
template_name = 'post_detail.html'
class BlogCreateView(CreateView):
model = Posts
template_name = 'post_new.html'
fields = ['title', 'author', 'body']
models.py
from django.db import models
class Posts(models.Model):
title = models.CharField(max_length=200, default='False')
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
)
body = models.TextField()
def __str__(self):
return self.title
class Meta:
verbose_name = 'Пост'
verbose_name_plural = 'Посты'
home.html
{% extends 'base.html' %}
{% block content %}
{% for post in object_list %}
<div class="post-entry">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
{% endfor %}
{% endblock content %}
base.html
{% load static %}
<html>
<head>
<title>Django blog</title>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400"
rel="stylesheet">
<link href="{% static 'css/base.css' %}" rel="stylesheet">
</head>
<body>
<div>
<header>
<div class="nav-left">
Home | About
</div>
</header>
{% if user.is_authenticated %}
<h3>Hi {{ user.username }}!</h3>
<p>Log out</p>
<div class="nav-right">
+ New Blog Post
</div>
{% else %}
<p>You are not logged in.</p>
Log In
{% endif %}
{% block content %}
{% endblock content %}
</div>
</body>
</html>
The title, body and author fields cannot be null. If you initially create these fields, there will be no error. But if we edit the post model, then we create fields for which there is no value in the pages table in this case. Then if we create new fields, we need to write a default value for each of them.
title = models.CharField(max_length=200, default='')
author =models.ForeignKey(
'auth.User',
default='',
on_delete=models.CASCADE, )
body = models.TextField(default='')
After these manipulations, do not forget to update the migrations
python manage.py makemigrations
python manage.py migrate
I make a site with multiple users, making posts with images and ability to add/remove friends.
So it's easy to make two different pages for post list and creating a new one. But of course it looks better when you can read posts and make new at the same place.
As I understand (learn django for less than a month), I can't connect 2 views to the same url, so the most logical way I see is to join 2 views in one, I also tried to play with template inheriting to render post form by including template, but actually it doesn't work.
Below you can see my views, Post model, and templates. Thank you for attention.
views.py:
from braces.views import SelectRelatedMixin
from . import models
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
class PostList(SelectRelatedMixin, generic.ListView):
model = models.Post
select_related = ('user',)
class CreatePost(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
models.py:
import misaka
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE, related_name = 'posts')
posted_at = models.DateTimeField(auto_now = True)
post_message = models.TextField()
message_html = models.TextField(editable = False)
post_image = models.ImageField(upload_to = 'postpics', blank = True)
def __str__(self):
return self.post_message
def save(self, *args, **kwargs):
self.message_html = misaka.html(self.post_message)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('posts:all')
class Meta:
ordering = ['-posted_at']
unique_together = ['user', 'post_message']
urls.py:
app_name = 'posts'
urlpatterns = [
path('', views.PostList.as_view(), name = 'all'),
path('new/', views.CreatePost.as_view(), name = 'create'),
]
post_form.html (template, that allows to make a new post, which will be seen in post_list.html):
{% extends 'posts/post_base.html'%}
{% block post_content %}
<div class="post-form">
<form action="{% url 'posts:create' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.post_message }}</p>
<p>{{ form.post_image }}</p>
<input id='post-submit' type="submit" value="Post">
</form>
</div>
{% endblock %}
post_list.html:
{% extends 'posts/post_base.html'%}
{% block post_content %}
<div class="post-container">
{% for post in post_list %}
<div class="current-post-container">
{% include 'posts/_post.html'%}
</div>
{% endfor %}
</div>
{% endblock %}
_post.html(pages, which render by Misaka):
<div class="post-info">
<h5 id='post-owner' >{{post.user.first_name}} {{post.user.last_name}}</h5>
<h6>{{ post.posted_at }}</h6>
<p>{{ post.message_html|safe }}</p>
<div>
<img class='post-image' src="/media/{{ post.post_image }}" alt="">
<div>
{% if user.is_authenticated and post.user == user and not hide_delete %}
<a href="{% url 'posts:delete' pk=post.pk %}" title = 'delete'>Delete</a>
{% endif %}
</div>
</div>
</div>
post_base.html:
{% extends 'base.html' %}
{% block content%}
{% block prepost %}{% endblock %}
{% block post_content %}{% endblock %}
{% block post_post %}{% endblock %}
{% endblock %}
EDIT:
Task was solved. I added two template_name strings to both of my views, so now they look like:
CreatePost in views.py:
class CreatePost(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
PostList in views.py:
class PostList(SelectRelatedMixin, generic.ListView):
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
You can put the post_create_form on the same page as post_list_view there is no need to make a separate view for post creation but You need to make ones for editing and deleting.
You can give all of these views the same HTML page with different URLs.
Using template_name = 'example/example.html' ,in Class_Based_Views.
I hope I understand your problem if not clarify more why you can't join two views in one.
def posts(request):
posts = Post.objects.all()
form = PostForm(request.POST or None, request.FILES or None)
if request.method == "POST":
if form.is_valid():
...
context={
'posts' : page_obj,
'create_or_update_post_form' : form,
}
return render(request, 'post/posts.html',context)
Do you struggle to do this in Class-based-view?
You can do easily with django class based views.
Create views as
from django.views.generic import ListView
from django.views.generic.edit import CreateView
class ModelCreate(CreateView):
model = ModelName
fields = ['field1', 'field2']
template_name = 'same_page.html'
success_url = reverse_lazy('list_view')
class ModelList(CreateView, ListView):
model = ModelName
fields = ['field1', 'field2']
paginate_by = 5
template_name = 'same_page.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['form'] = self.get_form()
return context
# If form post redirect to ModelCreate View
def post(self, request, *args, **kwargs):
return ModelCreate.as_view()(request)
app/urls.py
from django.urls import path
from app import views
path('list', views.ModelList.as_view(), name='list_view'),
path('add', views.ModelCreate.as_view(), name='add_view'),
Finally in templates/same_page.html
<div class="row">
<div class="col-sm-5">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<button type="submit" value="submit" class="btn btn-primary btn-sm float-right">Submit</button>
</form>
</div>
<div class="col-sm-5">
{% if object_list %}
{% for object in object_list %}
<p>{{object.field1}}</p>
<p>{{object.field2}}</p>
{% endfor %}
{% endif %}
</div>
</div>
Hope, this helps.
I've this template in my Django application for adding a training session:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<h1>New session</h1>
<form action="" method="post">{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-success" type="submit" value="Save" />
</form>
<p />
{% endblock content %}
The form contains a datetime field which appears as follows:
Is it possible to change this so instead of entering the datetime as text it can be selected from a calendar type icon? If so, how is this done?
This is my view:
class SessionCreateView(CreateView):
model = ClubSession
template_name = 'session_new.html'
fields = ['location', 'coach', 'date', 'details']
This is my model:
class ClubSession(models.Model):
location = models.CharField(max_length=200)
coach = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
date = models.DateTimeField(default=now)
details = models.TextField()
def __str__(self):
return self.location
def get_absolute_url(self):
return reverse('session_detail', args=[str(self.id)])
With crispy forms, I think you will need a form for this.
class ClubSessionForm(forms.ModelForm):
class Meta:
model = ClubSession
fields = ['location', 'coach', 'date', 'details']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['date'].widget.attrs.update({'type': 'datetime-local'})
class SessionCreateView(CreateView):
model = ClubSession
form_class = ClubSessionForm
template_name = 'session_new.html'
Docs
Keep in mind that not all browsers support <input type="datetime-local"> - I believe only Chrome and Opera do. If you need it working in all browsers, you'll need a JS solution.
Model Post has the boolean field moderation, which is intended for publishing after approval by admin users (which have user.is_staff as True.
There is a page "Post update", where an user (the author of the post) and admin users can updated the post info.
I want that the field moderation (which is a checkbox) is shown only if the user is an admin (user.is_staff == True).
models.py
class Post(models.Model):
...
moderation = models.BooleanField(default=True)
...
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'body', 'logo']
views.py
class PostUpdateView(PermissionRequiredMixin, UpdateView):
model = Post
fields = ['title', 'body', 'logo']
permission_required = 'post.can_mark_returned'
post_form.html
{% extends "base_generic.html" %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit" />
</form>
{% endblock %}
This similar question has some ideas for you.
I can also suggest overriding FormMixin.get_form_class() and using modelform_factory():
from django.forms import modelform_factory
class PostUpdateView(PermissionRequiredMixin, UpdateView):
model = Post
permission_required = 'post.can_mark_returned'
def get_form_class(self)
fields = ['title', 'body', 'logo']
if self.request.user.is_staff:
fields.append('moderation')
return modelform_factory(
self.model,
fields=fields)
I have a login system where you need to be logged in to submit a new post. My "New Post" page/form work fine and the content the user submits is properly posted in the database, how when it comes to displaying that content on the home page only the title and subtitle are shown (which are Charfields) and no the body text of the post (which is a text field).
inde.html
{% extends "blog/base.html" %}
{% block body_block %}
<div class="left">
{% if userposts %}
{% for posts in userposts %}
<div class="front-post">
<h2 class="post-title">{{ posts.post_title }}</h2>
<h3 class="post-sub-title">{{ posts.post_sub_title }}</h3>
<p class="post-author">{{ post.post_author }}</p>
<p class="post-date">{{ post.post_date }}</p>
<p class="post-body">{{ post.post_body }}</p>
</div>
{% endfor %}
{% endif %}
</div>
<div class="right">
<p>SIDE BAR</p>
</div>
{% endblock %}
views.py
from django.shortcuts import render
from blog.forms import UserForm,UserProfileInfoForm,AddPost
from blog.models import UserPosts
from django.contrib.auth import authenticate,login,logout
from django.http import HttpResponseRedirect,HttpResponse
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
# Create your views here.
def index(request):
post_list = UserPosts.objects.order_by('post_date')
post_dict = {'userposts':post_list}
return render(request, 'blog/index.html',context=post_dict)
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserProfileInfo(models.Model):
user = models.OneToOneField(User)
portfolio_site = models.URLField(blank=True)
profile_pic = models.ImageField(upload_to='profile_pics',blank='True')
def __str__(self):
return self.user.username
class UserPosts(models.Model):
post_title = models.CharField(max_length=100,unique=True)
post_sub_title = models.CharField(max_length=250,unique=False)
post_author = ''
post_date = models.DateField(auto_now=True)
post_body = models.TextField(max_length=1000,unique=False)
def __str__(self):
return str(self.post_title)
forms.py
from django import forms
from django.contrib.auth.models import User
from blog.models import UserProfileInfo,UserPosts
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta():
model = User
fields = ('username','email','password')
class UserProfileInfoForm(forms.ModelForm):
class Meta():
model = UserProfileInfo
fields = ('portfolio_site','profile_pic')
class AddPost(forms.ModelForm):
class Meta():
model = UserPosts
fields = '__all__'
Watch your naming. Your for loop variable is named posts (with an s at the end) but you're trying to display post.post_body. In some places it's working because you're using posts.post_title.
To fix this issue, rename posts to just post everywhere in your for loop.
{% for post in userposts %}
<div class="front-post">
<h2 class="post-title">{{ post.post_title }}</h2>
<h3 class="post-sub-title">{{ post.post_sub_title }}</h3>
<p class="post-author">{{ post.post_author }}</p>
<p class="post-date">{{ post.post_date }}</p>
<p class="post-body">{{ post.post_body }}</p>
</div>
{% endfor %}
Django will silently fail any expressions it can't evaluate in the templates, which is why nothing was being shown.