How to make django crispy form to hide a particular field? - python

I'm trying to make my date_modified field as hidden since I have passed datetime.now parameter on defining date_modified field in model.
model.py
class Guide(models.Model):
name = models.CharField(max_length=50)
sno = models.CharField(max_length=50)
date_created = models.DateTimeField(default=datetime.now, blank=True)
date_modified = models.DateTimeField(default=datetime.now, blank=True)
def __unicode__(self):
return unicode(self.name)
views.py
class GuideFormUpdateView(UpdateView):
model = Guide
fields = ['name', 'sno', 'date_modified']
template_name_suffix = '_update_form'
success_url = reverse_lazy('Guides')
corresponding form forms.py looks like
<form role="form" method="POST" action="{% url 'Guideform-edit' object.pk %}"
class="post-form form-horizontal" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy }}
<button type="submit" value="Upload" class="save btn btn-default btn-primary center-block">Update</button>
</form>
This form displays date_modified field. But I don't want this field on frontend instead I want the value of this field in model or db_table should get updated. I know how to hide this particular field in jquery but I don't want to touch those js tools. Is there any way to make crispy to exclude that particular field like {{ form|crispy|exclude:date_modified }} ..

Instead of using Generic Form that your UpdateView will use implicitly, create your custom Form. And in your custom Form change the widget of the date_modified field.
In your forms.py
from django.forms import ModelForm, HiddenInput
class GuideForm(ModelForm):
def __init__(self, *args, **kwargs):
super(GuideForm, self).__init__(*args, **kwargs)
self.fields['date_modified'].widget = HiddenInput()
class Meta:
fields = ('name', 'sno', 'date_modified', )
model = models.Guide
In your views.py
class GuideFormUpdateView(UpdateView):
model = Guide
form_class = forms.GuideForm
template_name_suffix = '_update_form'
success_url = reverse_lazy('Guides')
To automatically update date_modified whenever you update the record, you need to use attributes auto_now and auto_now_add instead of default. See Docs. So your model will be
class Guide(models.Model):
name = models.CharField(max_length=50)
sno = models.CharField(max_length=50)
date_created = models.DateTimeField(auto_now_add=True, blank=True)
date_modified = models.DateTimeField(auto_now=True, blank=True)
def __unicode__(self):
return unicode(self.name)

You can hide a field in a form class like so:
Field('field_name', type="hidden")
Where Field is from crispy_forms.layout
Don't forget that if he field cannot be left empty, you'll still need to pass an appropriate value before saving it.
Abstract example:
class GuideFormHiddenField(GuideFormUpdateView):
def __init__(self, *args, *kwargs):
Field('date_modified', type="hidden")
This is the easiest way. You can of course make an entirely new form, or implement your fields individual, and use a condition to determine the visibility of a certain field; which would be something like if User.is_authenticated(): ... .

I think this should work:
from django.forms.models import modelform_factory
class GuideFormUpdateView(UpdateView):
model = Guide
form_class = modelform_factory(Guide, widgets={"date_modified": HiddenInput })
fields = ['name', 'sno', 'date_modified']
template_name_suffix = '_update_form'
success_url = reverse_lazy('Guides')
See here modelform_factory.

Related

CharField of Form not showing up in Django Project

I have created Comment Model for my Project but the CharField is not showing in the LocalHost for some reason.
The submit button is working but there is no field to place text. I am trying to know why the CharField is not showing and how to show it in the website?
Here is the models.py
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
body = models.TextField(max_length=300)
def __str__(self):
return f"{self.post}-{self.user}-Comment No.{self.pk}"
Here is the views:
class Comment_create(CreateView):
model = Comment
fields = ['body']
template_name = 'blog/post_detail.html'
form = CommentModelForm
def form_valid(self, form):
post = get_object_or_404(Post, slug=self.kwargs['slug'])
form.instance.user = self.request.user
form.instance.post = post
return super().form_valid(form)
def form_invalid(self, form):
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('blog:post-detail', kwargs=dict(slug=self.kwargs['slug']))
Here is the forms.py
class CommentModelForm(forms.ModelForm):
body = forms.CharField(label='',
widget=forms.TextInput(attrs={'placeholder': 'Add a comment...'}))
class Meta:
model = Comment
fields = ('body',)
Here is the urls.py
path('blogs/<slug:slug>/comment/',
Comment_create.as_view(), name='comment-post'),
Here is the template:
<div class="container-fluid mt-2">
<div class="form-group row">
<form action="{% url 'blog:comment-post' post.slug %}" method="post" class="comment-form" action=".">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" class="btn btn-outline-success">
</form>
</div>
</div>
In your code, you made a mistake declaring form = CommentModelForm. This is incorect, the correct attribute is form_class, so:
class Comment_create(CreateView):
model = Comment
# fields = ['body'] # <--- this is redundant, since you already specified it in yout modelform
template_name = 'blog/post_detail.html'
# form = CommentModelForm <--- this is wrong
form_class = CommentModelForm # <--- this is the correct signature
# the rest of the view
Next, in your ModelForm, it is redundant to specify body field, since you already declared it in the Meta section:
class CommentModelForm(forms.ModelForm):
# body = forms.CharField(label='',
# widget=forms.TextInput(attrs={'placeholder': 'Add a comment...'}))
# This is redundand. You can specify input widget in Meta section
class Meta:
model = Comment
fields = ('body',)
If you want to modify the widgets look and feel, you can do it in the Meta section like this:
# previous code
class Meta:
model = Comment
fields = ['body',]
labels = {
'body':'your label'
}
widgets = {
'body' : Textarea(attrs={
'rows': '9',
'cols': '80',
}),
}
[EDIT: Missing {{form}} in template]
If you are missing form in your template context, that would be very strange, if you have shown us all you do in your views/forms. The context is usually populated by default, so you should have the lines:
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
set in your settings.py of the project.
You can always manually check if everything is ok, by overriding the context data function and populating it yourself:
# in your create view:
class Comment_create(CreateView):
#def all the other definitions and then override:
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs) # <-- this should populate your form, but let us do it manually nonetheless
form = self.form_class(self.request.POST or None)
context['form'] = form
return context
If this fails to work as well, then the issue is somewhere else, I believe.
Also, I see you set action attribute in your <form> tag of the template twice. That is also wrong.

How to pass data from Django Template to View

I have a template where customer details are displayed, along with a 'Create Lead' button at the bottom. This should take the user to the Lead creation form page where the customer field should be pre-selected.
I'm new to django. Based on responses of previous similar questions, I came up with the below code. But when I click the 'Create Lead' button, the url changes from "http://127.0.0.1:8000/sales/customer/21" to "http://127.0.0.1:8000/sales/customer/21/?customer_id=21" but nothing happens on the page. I tried with POST method and csrf token also, but it gives HTTP ERROR 405. Could some please help here.
Also, I've separate view for creating a Lead, which is sort of duplication of CreateView for Lead. And I believe that's not how it's supposed to be. What is the way to club them both in single view.
Below are the code snippets.
Models.py
class Customer(models.Model):
name = models.CharField(max_length=256)
email = models.EmailField(unique=True)
phone = models.PositiveIntegerField()
class Lead(models.Model):
customer = models.ForeignKey(Customer,related_name='Leads',on_delete=models.PROTECT)
destinations = models.CharField(max_length=256)
lead_source = models.CharField(max_length=256,choices=lead_source_choices,default='FNF')
lead_source_id = models.CharField(max_length=25,blank=True)
lead_status = models.CharField(max_length=25,choices=lead_status_choices,default='NEW')
remarks = models.TextField(blank=True)
trip_id = models.CharField(max_length=10,editable=False,unique=True,default="IN"+uuid.uuid1().hex[:5].upper())
creation_date = models.DateField(auto_now=True)
Forms.py
class LeadForm(forms.ModelForm):
class Meta:
model = Lead
fields = ('customer','destinations','lead_source','lead_source_id','lead_status','remarks')
class LeadFromCustomerForm(forms.ModelForm):
class Meta:
model = Lead
fields = ('destinations','lead_source','lead_source_id','lead_status','remarks')
Template
<form method="GET">
<input type="hidden" name="customer_id" value="{{customer.id}}">
<a href="{% url 'SalesApp:lead_create_from_customer' %}">
<button class="btn btn-warning btn-lg float-right" type="submit">Create Lead</button></a>
</form>
Urls.py
path('lead/create/',views.LeadCreateView.as_view(),name='lead_create'),
path('lead/create/customer/',views.LeadCreateFromCustomerView.as_view(),name='lead_create_from_customer')
Views.py
class LeadCreateView(LoginRequiredMixin,UserPassesTestMixin,CreateView):
form_class = LeadForm
model = Lead
def test_func(self):
return self.request.user.groups.filter(name='Sales').exists()
class LeadCreateFromCustomerView(LoginRequiredMixin,UserPassesTestMixin,CreateView):
form_class = LeadFromCustomerForm
model = Lead
def test_func(self):
return self.request.user.groups.filter(name='Sales').exists()
def form_valid(self,form):
customer_id = self.request.GET.get("value")
form.instance.customer = Customer.objects.get(id=customer_id)
return super(LeadCreateFromCustomerView,self).form_valid(form)

Django get PK dynamically from views.py

I have a view that renders a comment form along with a template:
views.py
def news(request):
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = Article.objects.get(pk=2)
print(comment.post)
comment.author = request.user.username
comment.save()
return HttpResponseRedirect('')
else:
form = CommentForm()
return render(request, '../templates/news.html', context={"form": form})
models.py
class Comment(models.Model):
post = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments', blank=True)
author = models.TextField()
text = models.TextField()
def __str__(self):
return self.text
forms.py
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('text',)
In views.py, where comment.post is getting assigned to Article objects, I want the pk to be applied dynamically. I tried doing it in the templates, where putting {{ article.pk }} in the templates output the right pk for the Article object but I wasn't sure how I'd go about applying it to my form.
The templates look simple: Article object, below it a comment form.
The problem is simple, I want the news(request) function to dynamically apply the pk of the current Article object in order to make the comment go to the right post.
You can either use the path if it's unique or you can just add a hidden field and set the article pk as value:
<input name="post" type="hidden" value={{ article.pk }} />
And your form:
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('text', 'post')
and you can access it from validated data in view.

My Django form wont' allow me to save tags

I currently have a form on my development site that I use to create new posts. I recently added a Tags model and a tags field in my Post model. I then added the tags field to my form. But when I create a post and select a tag and save it. Everything is saved except for the tag I selected. Heres my code
my models
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
title = models.CharField(max_length=120)
slug = models.SlugField(max_length=200, unique=True)
image = models.ImageField(upload_to=upload_location,
null=True,
blank=True,
width_field="width_field",
height_field="height_field")
height_field = models.IntegerField(default=0)
width_field = models.IntegerField(default=0)
content = models.TextField()
draft = models.BooleanField(default=False)
publish = models.DateField(auto_now=False, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
tags = models.ManyToManyField(Tag)
objects = PostManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:detail", kwargs={"slug": self.slug})
class Meta:
ordering = ["-timestamp", "-updated"]
class Tag(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=200, unique=True)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:tag_index", kwargs={"slug": self.slug})
class Meta:
ordering = ["-timestamp"]
my forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [
"title",
"content",
"image",
"draft",
"publish",
"tags"
]
my view
def post_create(request):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
form = PostForm(request.POST or None, request.FILES or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
messages.success(request, "Created !!")
return HttpResponseRedirect(instance.get_absolute_url())
template = "posts/post_form.html"
context = {
"form": form
}
return render(request, template, context)
my post form.html
{% extends 'posts/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="col-sm-6 col-sm-offset-3">
<h1>
Form
</h1>
<form method="POST" action="" enctype="multipart/form-data">{% csrf_token%}
{{ form|crispy}}
<input type="submit" value="Create Post" class="btn btn-default">
</form>
</div>
{% endblock content %}
any and all guidance is welcome
You have to save the many to many field using save_m2m()
So basically:
instance = form.save(commit=False)
instance.save()
instance.save_m2m()
Excerpt from the documentation:
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you’ve manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
Note that save_m2m() is required only when you do commit=False. If you just do form.save(), all data, including the many to many would be saved successfully

Excluding Fields in Django Class Based Views Not Working

Boy, it feels like I've tried EVERYTHING here, and I just can't get this form to render properly (e.g. with a subset of fields). Here's the relevant items (extra items removed):
models.py:
class Response(models.Model):
public = models.BooleanField(default=False)
question = models.CharField(max_length=255, default='', blank=True)
class ResponseForm(ModelForm):
class Meta:
model = Response
fields = ['question']
views.py:
class ResponseCreate(CreateView):
model = Response
fields = ['question']
response_form.html:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
I've read every bit of docs, and they suggest using exclude, excludes, field, fields in permutations of tuple, or list, and no matter what the "public" field seems to show up every time! Thoughts?
Thanks for your help!
It looks like you need to specify form_class on your view:
class ResponseCreate(CreateView):
model = Response
form_class = ResponseForm
Just I want to mention that you don't really need a ModelForm to be created separately.
Generic views really shine when working with models. These generic
views will automatically create aModelForm, so long as they can work
out which model class to use
You can just use this.
class Response(models.Model):
public = models.BooleanField(default=False)
question = models.CharField(max_length=255, default='', blank=True)
class ResponseCreate(CreateView):
model = Response
fields = ['question']
You can use exclude field in your class form:
class ResponseForm(ModelForm):
class Meta:
model = Response
exclude = ['field_to_exclude']
class ResponseCreate(CreateView):
model = Response
form_class = ResponseForm

Categories