Django: One model, different forms and widgets? - python

I try to find the best way to have a field with multiple content types.
What I've done so far is a Contact model with a contacttype CharField:
class Contact(models.Model):
CONTACT_TYPES = (
('email', 'Email'),
('phone', 'Phone'),
('address', 'Address'),
('facebook', 'Facebook'),
('linkedin', 'LinkedIn'),
('youtube', 'Youtube'),
('twitter', 'Twitter'),
('google', 'Google'),
)
teammember = models.ForeignKey(TeamMember)
description = models.CharField(max_length=100, null=True)
contacttype = models.CharField(max_length=100, choices= CONTACT_TYPES, default='email')
contact = models.TextField()
My goal is to let the user add different contact informations, which'll be listed on the profile page, but with only one model.
I was thinking about a class for each ModelForm:
class ContactForm(ModelForm):
def __init__(self, data, *args, **kwargs):
super(ModelForm, self).__init__(data, *args, **kwargs)
self.contacttype = ""
class Meta:
model = Contact
fields = ['description', 'contact']
widgets = {'contact': TextInput()}
def clean_contacttype(self):
return self.contacttype
class ContactEmailForm(ContactForm):
def __init__(self, data, *args, **kwargs):
super(ContactForm, self).__init__(data, *args, **kwargs)
self.contacttype = "email"
class Meta(ContactForm.Meta):
model = Contact
fields = ['description', 'contact']
widgets = {'contact': EmailInput()}
class ContactPhoneForm(ContactForm):
def __init__(self, data, *args, **kwargs):
super(ContactForm, self).__init__(data, *args, **kwargs)
self.contacttype = "phone"
class Meta(ContactForm.Meta):
model = Contact
fields = ['description', 'contact']
widgets = {'contact': TextInput()}
def clean_contact(self):
cleaned_data = super(ContactForm, self).clean()
contact = cleaned_data.get("contact")
# Perform some phone number validations
return contact
Then, in my view, I would choose the correct form depending on the request argument (ex: /contact/add/email or /contact/add/phone).
I'm trying to find the most elegant way to do this, so any help is welcome.
Thanks for reading.

if you want to use one html file, then this is one of solutions.
class YourView(TemplateView):
template_name = 'your_template.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if context['type'] == 'phone':
context['form'] = ContactPhoneForm
elif ...
if you set your view, then
<form action="{% url "your view" %}" enctype="multipart/form-data" method="POST">{% csrf_token %}
{% if form.contenttype == 'phone' %}
{% include "partials/contact_phone_form.html" with form=form %}
{% elif form.contenttype == 'email' %}
{% include "partials/contact_email_form.html" with form=form %}
{% else %}
do something.
{% endif %}
</form>
and, contact_phone_form.html and contact_email_form is looks like
anything.
<input name='phone'>
anything.
this solution is for one template, multi form.
if you want to do multi template for specific form, then you can use this.
class YourView(TemplateView):
template_name = 'your_template.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if context['type'] == 'phone':
template_name = 'phone_form_template.html'
elif context['type'] == 'email':
template_name = 'email_form_template.html'
...

I would use forms.Form depending on the request argument like:
/contact/add/?type=email or /contact/add/?type=phone
and so you can use it like (not tested code):
class ContactForm(forms.Form):
def __init__(self, *args, **kwargs):
super(CreateUserquestionnaireForm, self).__init__(*args, **kwargs)
if "email" in self.data:
... do some thing like change contact type or widget
if "phone" in self.data:
... do some thing like change contact type or widget

Related

Form is invalid but no errors

Whenever I submit the form, it is invalid and there is no error message attached to it when I try to read it with form.errors; it's empty. Here is what I have:
models.py
class Project(models.Model):
project = models.CharField(unique=True, max_length=50)
is_active = models.BooleanField(default=False)
forms.py
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column, Submit, Field
class SelectProjectForm(forms.Form):
def __init__(self, active_choices, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['is_active'] = forms.ChoiceField(choices=active_choices, widget=forms.Select)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Row(
Column(Field('is_active'))
),
Row(
Column(FormActions(Submit('activate', 'Activate Project')))
),
)
views.py
class ProjectSettings(LoginRequiredMixin, TemplateView):
template_name = 'Home/project_settings.html'
def get(self, request, *args, **kwargs):
active_choices = []
for project in Project.objects.all():
active_choices.append((project.id, project.project),)
return render(request, self.template_name, {'form': SelectProjectForm(active_choices)})
def post(self, request, *args, **kwargs):
if 'activate' in request.POST:
form = SelectProjectForm(request.POST)
if form.is_valid():
....
messages.error(request, 'Something went wrong')
return redirect('project_settings')
project_settings.html:
<div>
{% load crispy_forms_tags %}
{% crispy form %}
</div>
I think the problem might be in the POST method in views where I initialize the form, but I don't know how to pass the active_choices parameter in post. If that is not the problem then I am lost.

How to add custom method in django forms

I am new to django, and I am creating a vacation application. I want to be able to when I create a new trip, the user that created the trip becomes a member of that trip.
here is my models.py file:
class Trip(models.Model):
trip_name = models.CharField(max_length=255,unique=False)
start_date = models.DateField(default=datetime.date.today)
end_date = models.DateField(default=datetime.date.today)
slug = models.SlugField(allow_unicode=True,unique=True)
members = models.ManyToManyField(User,through='TripMember')
def __str__(self):
return self.trip_name
def save(self,*args,**kwargs):
self.slug = slugify(self.trip_name)
super().save(*args,**kwargs)
def get_absolute_url(self):
return reverse('trips:single',kwargs={'slug':self.slug})
class Meta:
ordering = ['start_date']
class TripMember(models.Model):
trip = models.ForeignKey(Trip,null=True,related_name='memberships',on_delete=models.SET_NULL)
user = models.ForeignKey(User,null=True,related_name='user_trips',on_delete=models.SET_NULL)
def __str__(self):
return self.user.username
class Meta:
unique_together = ('trip','user')
this is my forms.py file:
class TripCreateForm(forms.ModelForm):
class Meta:
fields = ('trip_name','start_date','end_date')
model = Trip
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["trip_name"].label = "Trip Name"
self.fields["start_date"].label = "Start Date"
self.fields["end_date"].label = "End Date"
here is my views.py file:
class CreateTrip(CreateView):
form_class = TripCreateForm
template_name = 'trips/trip_form.html'
and my trip_form.html page:
<form action="{% url 'trips:create' %}" method="post" id='tripForm'>
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class="btn btn-primary btn-large" value="Create">
</form>
Where would I put the code to set the user as a tripmember and why?Also, if there is a better way to have set this up please let me know! I was gonna put it in the save part of the model but I am not quite sure if that is correct. Thanks!
You can override the form_valid() method of the CreateTrip class in your view:
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
# add the current user to the members list of the trip
user = self.request.user
self.object.members.add(user)
return super().form_valid(form)

Django form.is_valid() failing class based views - Form, SingleObject, DetailMixins

I have two apps, here we will call them blog and comments.
Comments has a Comment model. Blog has a blog Model. Comments has a CommentForm. Blog has a DetailView.
I want my CommentForm to appear on by Blog DetailView, so people can submit comments from the blog detail page.
The form renders OK - it makes a POST request, it redirects to get_success_url() but (I've added a couple of prints to views.py - see below) in testing in views.py to see if the form data is received I see the form.is_valid() path is not met, and I don't understand why.
I'm essentially trying to follow this, the 'alternative better solution':
https://docs.djangoproject.com/en/2.2/topics/class-based-views/mixins/#using-formmixin-with-detailview
blog/views.py
class CommentLooker(SingleObjectMixin, FormView):
template_name = 'blogs/blog_detail.html'
form_class = CommentForm
model = blog
def get_object(self):
#self.team = get_object_or_404(team, team_id=self.kwargs['team_id'])
#queryset_list = blog.objects.filter(team = self.team)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
return get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super(CommentLooker, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('blogs:teams')
class blogDisplay(View):
def get(self,request,*args,**kwargs):
view = blogFromteamContentView.as_view()
return view(request, *args, **kwargs)
def post(self,request,*args,**kwargs):
view = CommentLooker.as_view()
return view(request,*args,**kwargs)
class blogFromteamContentView(LoginRequiredMixin, DetailView):
model = blog
template_name = 'blogs/blog_detail.html'
# override get_object so we can use blog_id when we use this class in urls.py
# otherwise DetailViews expect 'pk' which defaults to the primary key of the model.
def get_object(self):
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
return get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
def get_context_data(self, **kwargs):
context = super(blogFromteamContentView, self).get_context_data(**kwargs)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
# get the list of blogs for a given blog id and team id combination.
context['queryset'] = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
# get and set things related to ability to associate comments to a blog.
initial_data = {
"content_type": blog.get_content_type,
"object_id": blog.blog_id
}
comments = blog.comments # uses the #property set in this class.
comment_form = CommentForm(self.request.POST or None, initial=initial_data)
if comment_form.is_valid():
print(comment_form.cleaned_data)
else:
print('invalido!')
context['comment_form'] = comment_form
return context
blog/models.py
class Blog(models.Model):
team= models.ForeignKey(Team, on_delete=CASCADE)
blog_id = models.AutoField(primary_key=True)
blog_name = models.CharField(
max_length=100, verbose_name='Blog Name')
blog/urls.py
path('teams/<int:team_id>/blogs/<int:blog_id>/', blog.blogDisplay.as_view(), name='detail'),
blog_detail.html
<div>
<p class="lead"> Comments </p>
<form method="POST" action="."> {% csrf_token %}
{{ comment_form}}
<input type="submit" value="Post Comment" class="btn btn-primary">
</form>
<hr/>
{% for comment in blog.comments.all %}
<blockquote class="blockquote">
<p>{{ comment.content }}</p>
<footer class="blockquote-footer"> {{ comment.user }} | {{ comment.timestamp|timesince }} ago </footer>
</blockquote>
<hr/>
{% endfor %}
comments/forms.py
from django import forms
class CommentForm(forms.Form):
content_type = forms.CharField(widget=forms.HiddenInput)
object_id = forms.IntegerField(widget=forms.HiddenInput)
parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
content = forms.CharField(widget=forms.Textarea)
edit:
after using print(comment_form.errors): object_idEnter a whole number.
suggesting my initial_data might be the problem. In fact both content_type and object_id in my initial_data were problems. I was asking for blog.blog_id - I.e. using the class, not an instance. So I changed
get_context_data:
def get_context_data(self, **kwargs):
context = super(blogFromteamContentView, self).get_context_data(**kwargs)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
# get the list of blogs for a given blog id and team id combination.
context['queryset_list_recs'] = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
instance = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
initial_data = {
"content_type": instance.get_content_type,
"object_id": blog_id_
}
and to my views.py:
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
print('valido')
c_type = comment_form.cleaned_data.get("content_type")
content_type = ContentType.objects.get(model=c_type)
obj_id = comment_form.cleaned_data.get('object_id')
content_data = comment_form.cleaned_data.get("content")
new_comment, created = Comment.objects.get_or_create(
user = self.request.user,
content_type = content_type,
object_id = obj_id,
content = content_data
)
else:
print('postinvalido!')
return super(CommentLooker, self).post(request, *args, **kwargs)
This (inappropriate print statements aside) now appears to give intended behaviour.
after using print(comment_form.errors):
object_id
List item
Enter a whole number.
suggesting my initial_data might be the problem. In fact both content_type and object_id in my initial_data were problems. I was asking for blog.blog_id - I.e. using the class, not an instance. So I changed
get_context_data:
def get_context_data(self, **kwargs):
context = super(blogFromteamContentView, self).get_context_data(**kwargs)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
# get the list of blogs for a given blog id and team id combination.
context['queryset_list_recs'] = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
instance = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
initial_data = {
"content_type": instance.get_content_type,
"object_id": blog_id_
}
and to my views.py:
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
print('valido')
c_type = comment_form.cleaned_data.get("content_type")
content_type = ContentType.objects.get(model=c_type)
obj_id = comment_form.cleaned_data.get('object_id')
content_data = comment_form.cleaned_data.get("content")
new_comment, created = Comment.objects.get_or_create(
user = self.request.user,
content_type = content_type,
object_id = obj_id,
content = content_data
)
else:
print('postinvalido!')
return super(CommentLooker, self).post(request, *args, **kwargs)
This (inappropriate print statements aside) now appears to give intended behaviour. I'm unclear why an instance of CommentForm needs to be created inside the post method - it feels like I'm doing something wrong here.

ChoiceField from model - Django - MongoEngine

I'm trying to make this work, with no success so far.
I'm using Django 1.6.5 and mongoengine 0.8.7.
I have some ReferenceField field types in my models.py, which I need to show in a Choice Field type, into a forms.py file.
So, these choices must be filled with the queryset from the referenced (or related models). So when I for example choose to register something in my app, I can relate for example 'users' to 'brands' or 'causes', etc...
Anyways, this was a brief explanation of what I'm trying to achieve with my app, here's my models.py:
from mongoengine import *
class Brand(DynamicDocument):
name = StringField(min_length=3,max_length=10,unique=True)
admins = ListField(ReferenceField("Peer", dbref=True))
campaigns = ListField(ReferenceField("Campaign"))
peers_partner = ListField(ReferenceField("Peer"))
payments = ListField(ReferenceField("Payment_Campaign"))
medias = ListField(EmbeddedDocumentField("Media"))
description = StringField(min_length=10,max_length=500)
socials = ListField(DictField())
def __unicode__(self):
return self.name
#property
def pic_profile(self):
for x in self.medias:
if x.tag == "profile":
return x.url
#property
def pic_banner(self):
for x in self.medias:
if x.tag == "banner":
return x.url
#property
def video_profile(self):
for x in self.medias:
if x.tag == "video":
return x.url
As you can see, there are some ReferenceField fields on my class, these are relationships with other models, but I'm still stuck trying to populate a choice field from forms.py with these references, here's my forms.py:
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, ButtonHolder, Submit, Field, Div
from bson.objectid import ObjectId
from mongoengine.queryset import Q
from mongoengine import *
from hdb.brand.models import *
class Form_save_brand(forms.Form):
name = forms.CharField()
admins = forms.ModelChoiceField(queryset=Brand.objects.get_or_create(id=Peer),empty_label="")
campaigns = forms.ChoiceField()
peers_partner = forms.ChoiceField()
payments = forms.ChoiceField()
medias = forms.ChoiceField()
socials = forms.ChoiceField()
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop('instance', None)
super(Form_save_brand, self).__init__(*args, **kwargs)
if self.instance:
self.fields['name'].initial = self.instance.name
self.fields['admins'].initial = self.instance.admins
self.fields['campaigns'].initial = self.instance.campaigns
self.fields['peers_partner'].initial = self.instance.peers_partner
self.fields['payments'].initial = self.instance.payments
self.fields['medias'].initial = self.instance.medias
self.fields['socials'].initial = self.instance.socials
def save(self, commit=True):
brand = self.instance if self.instance else Brand()
brand.name = self.cleaned_data['name']
brand.admins = self.cleaned_data['admins']
brand.campaigns = self['campaigns']
brand.peers_partner = self.cleaned_data['peers_partner']
brand.payments = self.cleaned_data['payments']
brand.medias = self.cleaned_data['medias']
brand.socials = self.cleaned_data['socials']
if commit:
brand.save()
return brand
The only field where I'm doing tests it's the fisrt ReferenceField (admins), which is where I'm stuck, if I manage to solve this I can continue with the other ones.
This is the traceback from Django:
NameError at /brand/nuevo/
name 'Peer' is not defined
Request Method: GET
Request URL: http://localhost:9000/brand/nuevo/
Django Version: 1.6.5
Exception Type: NameError
Exception Value:
name 'Peer' is not defined
Exception Location: /home/kkoci/hipeers/hweb/hweb/hweb/brand/forms.py in Form_save_brand, line 11
Python Executable: /home/kkoci/hipeers/hweb/hipeersweb/bin/python
As you can see right now the query it's like (id=Peer) but I've tried with (id=field), (id=name), etc... with no luck.
I hope I've explained myself...
Any ideas?
Any help would be greatly appreciated, please I'm stuck with this.
Thanks in advance!
EDIT
This is how I initiliaze the form in views.py:
class AddBrand(CreateView):
model = Brand
form_class = Form_save_brand
def get_template_names(self):
return ["brand/brand_nuevo.html"]
def get_success_url(self):
return reverse('list')
def brand_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
messages.success(self.request, "The brand has been created.")
return super(AddBrand, self).brand_valid(form)
Then in template brand/brand_nuevo.html:
{% extends "base.html" %}
{% block body %}
<form action="." method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit"/>
</form>
{% endblock body %}
So, what you need is to instantiate the Form_save_brand with a specific peer so you can work out the dependencies and what is shown. This can be achieved by this (most of the code is kept the same)
You have to change the form to the following:
class Form_save_brand(forms.Form):
name = forms.CharField()
campaigns = forms.ChoiceField()
peers_partner = forms.ChoiceField()
payments = forms.ChoiceField()
medias = forms.ChoiceField()
socials = forms.ChoiceField()
def __init__(self, peer, *args, **kwargs):
# we pass a peer parameter that will be used in the queryset query
self.instance = kwargs.pop('instance', None)
self.fields['admins'] = forms.ModelChoiceField(queryset=Brand.objects.get_or_create(id=peer),empty_label="")
and then, on your view
class AddBrand(CreateView):
model = Brand
# you don't need a form class here
def get_form(self, form_class=None):
return Form_save_brand(<you set the peer value here>, **self.get-form_kwargs())

Using formset with ContentType

I'm having trouble using the UpdateView for a view consisting of a form and formset.
I have the following models: Item and Picture.
Picture is defined as:
class Picture(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, blank=False)
content_type = models.ForeignKey(ContentType, verbose_name="content type",
related_name="content_type_set_for_%(class)s")
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id")
I have several models that contain pictures. For example, in the Item model:
class Item(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, blank=False)
pictures = generic.GenericRelation(Picture)
I have the following ItemCreateForm:
class ItemCreateForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ItemCreateForm, self).__init__(*args, **kwargs)
class Meta:
model = Item
The PictureForm:
class PictureForm(forms.ModelForm):
id = forms.IntegerField(widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
super(PictureForm, self).__init__(*args, **kwargs)
def save(self):
data = self.cleaned_data
obj = Picture(**data);
# do something to obj
# obj.save()
class Meta:
model = Picture
fields = ['id', 'name']
And the view:
class ItemUpdateView(UpdateView):
form_class = ItemCreateForm
template_name = 'item/new.html'
model = Item
success_url = '/items/'
def get_context_data(self, **kwargs):
context = super(ItemUpdateView, self).get_context_data(**kwargs)
item = context['object']
# Dont' create any extra forms when showing an update view
PictureFormSet = formset_factory(PictureForm, extra=0)
return {'form': kwargs['form'],
'picture_formset': UploadFormSet(initial = [ model_to_dict(a) for pic in item.pictures.all()])}
def post(self, request, *args, **kwargs):
self.object = self.get_object()
item_form = ItemCreateForm(request.POST, instance=self.object)
if item_form.is_valid():
item = item_form.save(commit=False)
item.save()
# How do update the pictures?
This is my urls.py:
url(r'^items/(?P<pk>\d+)/update/$', ItemUpdateView.as_view(), name='item_update')
The template:
<form action="" method="post" enctype="multipart/form-data">
{% for field in form %}
# do something
{% endfor %}
{{ picture_formset.management_form }}
{% for form in picture_formset.forms %}
# do something
{% endfor %}
<input name="commit" type="submit" value="Submit" />
</form>
I'm new to Django.
The user can dynamically(via jQuery) add/remove pictures through the Picture form in the single template that is used to display the item and multiple pictures.
1 I had to include the id as a hidden field for the picture, otherwise the pictures will be inserted instead of an Update. QN: Is there a better way to do this?
2 How do I update the picture model? Currently request.POST doesn't have all the fields in the model, thus the model is complaining of NULL fields? I'm totally at lost how to deal with formset in an UpdateView and is not the main form, like a simple example of UpdateView with the pk in the url.
PictureFormSet = formset_factory(PictureForm)
picture_formset = PictureFormSet(request.POST, request.FILES)
for picture_form in picture_formset.forms:
picture_form.save()

Categories

Resources