I have a TextField intended to store a large amount of text that can be logically split into 10 parts. I thought that it would make sense to create 10 separate Textareas, each for one logical part. Thus, I subclassed MultiWidget and MultiValueField like so:
class MultiWidget(forms.widgets.MultiWidget):
template_name = "custom_content_widget.html"
attrs = {"class": "textarea form-control"}
def __init__(self, attrs=None):
widgets = [Textarea()] * 10
super(MultiWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return value
return ["", "", "", "", "", "", "", "", "", ""]
class ContentField(MultiValueField):
widget = MultiWidget
def __init__(self, *args, **kwargs):
# Define one message for all fields.
error_messages = {
'required': 'This field is required.',
}
# Or define a different message for each field.
fields = [CharField()] * 10
super(ContentField, self).__init__(
error_messages=error_messages, fields=fields, require_all_fields=True, *args, **kwargs)
# self.helper = FormHelper()
# self.helper.layout = Layout(
#
# )
def compress(self, data_list):
return " ".join(data_list)
with the custom_content_widget.html being just
{% for subwidget in widget.subwidgets %}
{% with widget=subwidget %}
{% include widget.template_name %}
{% endwith %}
{% endfor %}
Simple model and form in which I'd like to use this multiwidget
class Opinion(models.Model):
content = models.TextField()
class OpinionForm(forms.ModelForm):
content = ContentField()
class Meta:
model = Opinion
fields = ('__all__')
The problem is that when I use content in my form's HMTL as {{ form.content | as_crispy_field }} it renders pretty ugly
and I'd like all of the Textareas to be rendered one under the other. The main issue here is that textarea is rendered as
<textarea name="content_0" cols="40" rows="10" class="textarea" required id="id_content_0">
</textarea>
while "normal" TextField is rendered as
<textarea name="content" cols="40" rows="10" class="textarea form-control" required id="id_content">
</textarea>
and I have no clue how could I force the class of the widget to be textarea form-control instead of textarea. Initially, I found this question and also this blog but all they do is just to properly group widgets into rows and columns. Is there anything I am missing here?
The key was to pass attributes dict with "class": "textarea form-control" to the MultiWidget constructor as follows
class ContentField(MultiValueField):
widget = MultiWidget({"class": "textarea form-control"})
def __init__(self, *args, **kwargs):
# Define one message for all fields.
error_messages = {
'required': 'This field is required.',
}
# Or define a different message for each field.
fields = [CharField()] * 10
super(ContentField, self).__init__(
error_messages=error_messages, fields=fields, require_all_fields=True, *args, **kwargs)
def compress(self, data_list):
return " ".join(data_list)
Related
Crispy forms use the string representation of objects provided by the str method of the objects' class: I need to change this behaviour (please, help me).
In my crispy form the labels of the choices in a CheckboxSelectMultiple() field populate from the default str method of the objects represented. The set of objects is defined in a list containing ids, with those ids crispy calls the str methods.
Is it possible to write a custom string representation (for instance as a class' #property) and tell crispy to use that instead?
If yes, which point in the pipeline would give the best programmer's practice (models/views/forms/template) ?
this image is just a dummy example for better illustrating the problem
Overriding the default str method provides the desired labelling (as suggested in this post) but is totally unacceptable because of side effects.
models.py
class School(models.Model):
nice_name_for_forms = models.CharField(max_length=100)
#property
def label_from_instance(self):
return '%s' % (self.nice_name_for_forms)
views.py
school_list = School.objects.all().values_list('id', flat=True)
form = MyForm(request.POST, school_list=school_list)
forms.py
class MyForm(forms.ModelForm):
class Meta:
model = MyForm
fields = '__all__'
labels = {'schools' : 'My Schools'}
widgets = {'schools' : forms.CheckboxSelectMultiple()}
def __init__(self, *args, **kwargs):
self.school_list = kwargs.pop('school_list')
super().__init__(*args, **kwargs)
self.fields['schools'].queryset = self.fields['schools'].queryset.filter(id__in=self.school_list).distinct()
self.helper = FormHelper()
self.helper.use_custom_control = False
self.helper.layout = Layout(
Row(CheckboxAllFieldCompact('schools', wrapper_class='col-4 col-md-2'))
checkbox_all_field.html
<!-- crispy/checkbox_all_field.html -->
{% load crispy_forms_field %}
{% load i18n %}
<div id="div_checkbox_all_{{ field.html_name }}" class="no-form-control control-group {{ wrapper_class }}">
<div class="controls" style="max-height:250px;overflow:auto">
<label for="{{ field.name_for_label }}" class="label_title inline">{{ field.label }}</label>
<br />
<label class="block">
<button id="check_all_{{ field.html_name }}" type="button" class="btn btn-default btn-sm" actif="false">{% translate 'Select all' %}</button>
</label>
{% crispy_field field %}
</div>
</div>
I am trying to edit manytomanyfield data. Here is my model
class Permission(models.Model):
shop = models.ForeignKey(Shop, on_delete=models.SET_NULL, null=True)
permission_title = models.CharField(max_length=255)
class Meta:
ordering = ["-id"]
def __str__(self):
return self.permission_title
class Roles(models.Model):
shop = models.ForeignKey(Shop, on_delete=models.SET_NULL, null=True)
role_title = models.CharField(max_length=255)
permissions = models.ManyToManyField(Permission)
class Meta:
ordering = ["-id"]
def __str__(self):
return self.role_title
Here in these models i have a model called permission. this model is in a manytomanyfield relation with Roles model. I want to edit this manytomanyfield. I have already did the creation part. But now i want to edit the data and I want to show the data in the templates, I chose while creating a role. I want to show it an input type checkox
Here is my views:-
def createRolesView(request, shop_id):
shopId = get_object_or_404(Shop, pk=shop_id)
permissions = Permission.objects.filter(
shop=shopId.id
)
if shopId.user == request.user:
if request.method == "POST":
role_title = request.POST.get("role_title")
shop = Shop.objects.get(id=shopId.id)
permissions = request.POST.getlist("permissions")
rl = Roles(
role_title = role_title,
shop = shop,
)
rl.save()
for p in permissions:
rl.permissions.add(p)
rl.save()
return redirect(f"/adminpanel/roles/{shopId.id}/")
args = {
"shopId": shopId,
"permissions": permissions,
}
return render(request, "roles/create-role.html", args)
else:
return redirect("warning")
def editRolesView(request, role_id, shop_id):
shopId = get_object_or_404(Shop, pk=shop_id)
roleId = get_object_or_404(Roles, pk=role_id)
if shopId.user == request.user:
if request.method == "POST":
roleId.role_title = request.POST.get("role_title")
shop = Shop.objects.get(id=shopId.id)
# roleId.permissions_set.all()
args = {
"shopId": shopId,
"roleId": roleId,
}
return render(request, "roles/edit-role.html", args)
else:
return redirect("warning")
Suggestions only, I haven't coded this myself.
[Edit. This question caused an "itch", so I coded it up to conform I was right. Skip ahead for a pretty generic edit M2M view ]
Two simple tables
One showing the current permissions assigned to the role, which has the queryset roles.permissions.all(), along with a "remove" checkbox for each.
The other showing the available permissions with an "add" checkbox for each. The queryset might be permissions.exclude( roles__pk=this_role.pk) (to exclude those already added, although it's harmless to add something already added). You should also exclude any permissions which this roles instance is not allowed to acquire.
You might use Django Formsets, but it's also rather easy to process raw data from request.POST in this case. Your template would basically be a form containing instances of
<input type="checkbox" name="add" value="{{permission.pk}}" >
and
<input type="checkbox" name="remove" value="{{permission.pk}}" >
with appropriate descriptive text gathered from the permission instance.
If it were me I'd display them as tables left and right, but as long as they are inside a <form> with Submit (and Done?) buttons, it's not relevant.
In your POST handling, you'd get the checked pk values with request.POST.getlist('add') and request.POST.getlist('remove'). For each one, revalidate that it was in the original queryset that was rendered into a form, and if the pk values are acceptable
role.permissions.add( permission) # or .remove( permission)
(where permission is the Permissions instance that's been revalidated)
After Submit of the above you'd probably redirect back to the same again so that the user can see that the requested additions and removals have happened. The "Done" button will redirect elsewhere( if "Done" in request.POST ... )
[Edit] The result of causing me an itch I had to scratch ....
class GenericEditM2MView( DetailView):
#model = Model # required as per DetailView
# template_name = # as per DetailView
#m2m_fieldname = None # no longer required if unique: the name of the model's m2m field to operate on
remove_qs = None # defaults to .all() of the m2m field
success_url = '.' # defaults to displaying the modified m2m relationship unless done
done_url = None # where to go if submit="Done", defaults to success_url
"""
template_name must define a form full of checkboxes, obtained from
{% for p in add_qs %}
<input type="checkbox" name="add" value="{{p.pk}}" > {% endfor}
{% for p in remove_qs %}
<input type="checkbox" name="remove " value="{{p.pk}}" > {% endfor}
default is to return to this same view after submit, to show that the changes
have been made. You can supply <input type="submit" name="submit" value="done" />
which will go to done_url instead of success_url
example use:
class PenStockM2MView( GenericEditM2MView):
template_name = 'playpen/edit_m2m.html'
model = PenStock
# m2m_fieldname = 'name' # works it out if ony one M2M field on the model
done_url = '/playpen/OK'
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# everything works without this __init__ provided self.m2m_fieldname is present and correct.
# if model has only one m2m field, locate it via _meta as default.
# Also check m2m_fieldname is m2m because very confusing errors later if it's not!
f = getattr(self, 'm2m_fieldname', None)
m2m_fieldnames = [ field.name for field in self.model._meta.get_fields() if field.many_to_many ]
model_name = self.model.__name__
if f and not f in m2m_fieldnames:
raise AttributeError( f'field "{f}" is not a many-to-many field in {model_name}')
if not f:
if len( m2m_fieldnames ) == 1:
self.m2m_fieldname = m2m_fieldnames[0]
else:
raise AttributeError( f'Cannot identify a unique many-to-many field in {model_name}' )
def get_add_queryset(self):
field = getattr( self.object, self.m2m_fieldname)
remove_qs = self.get_remove_queryset()
already_there = remove_qs.values_list('pk', flat=True)
return remove_qs.model.objects.exclude( pk__in = already_there) # is qs.model documented?
def get_remove_queryset(self):
if hasattr( self, 'remove_queryset'):
return self.remove_qs
remove_qs = getattr( self.object, self.m2m_fieldname)
return remove_qs.all()
def get_context_data( self, **kwargs):
context = super().get_context_data( **kwargs)
context['add_qs'] = self.get_add_queryset()
context['remove_qs'] = self.get_remove_queryset()
return context
def post( self, request, *args, **kwargs):
self.object = self.get_object()
add = request.POST.getlist('add')
remove = request.POST.getlist('remove')
add_objs = list( self.get_add_queryset().filter(pk__in=add) )
remove_objs = list( self.get_remove_queryset().filter(pk__in=remove) )
field = getattr( self.object, self.m2m_fieldname )
field.add( *add_objs)
field.remove( *remove_objs)
return HttpResponseRedirect( self.get_done_url() or self.get_success_url() )
def get_success_url(self):
return self.success_url
def get_done_url( self):
done = self.request.POST.get("submit", None)
if done == "done" and hasattr(self, 'done_url'):
return self.done_url
return None
And here's a template (not completely generic. Using Bootstrap in base.html.)
<div class="row">
<div class="col-md-3 col-sm-4 col-xs-6">
<h1> Remove </h1>
<table class="table tbl">
{% for p in remove_qs %}
<tr><td>{{p.name }}</td><td><input type="checkbox" name="remove" value="{{p.pk}}" form="the-form" class="BigCheckbox"></td></tr>
{%endfor %}
</table>
</div>
<div class="col-md-3 col-sm-4 col-xs-6">
<h1> Add </h1>
<table class="table tbl">
{% for p in add_qs %}
<tr><td>{{p.name }}</td><td><input type="checkbox" name="add" value="{{p.pk}}" form="the-form" class="BigCheckbox"></td></tr>
{%endfor %}
</table>
</div>
</div>
<div class="row">
<form method="post" id="the-form"> {% csrf_token %}
<input type="submit" name="submit" value="submit" />
<input type="submit" name="submit" value="done" />
</form>
</div>
After struggling with this issue for a while, I am hoping someone here can point me in a more productive direction.
I am trying to take an indeterminate number of variables in a database (obtained through a different template) and render them on a webpage, each variable with a simple data entry form, to be saved back to the database. Basically, it's a tracker for analysis. Say I want to track my daily sleep, running time, and calorie intake (the variables). I have those saved in a database as variables and want to call upon those variables and show them on a webpage with a daily entry form. I am using a "for" loop right now and it renders the way I want it to, with the variable name and the form, but it is only saving the last item in the variable list. How do I amend the code below such that when I hit the save button for each form rendeded, it saves the information for that variable (not just the last one rendered).
Below is the code. Any and all help would be infinitely appreciated.
Models...
class Variable(models.Model):
date_added = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
ENTRY_TYPE_CHOICES = [
('numeric', 'enter a daily number'),
('scale', 'rate daily on a scale of 1-10'),
('binary', "enter daily, 'yes' or 'no' "),
]
variable = models.CharField(max_length=50, default='')
entry_type = models.CharField(max_length=50, choices=ENTRY_TYPE_CHOICES, default="numeric")
def __str__(self):
return self.variable
class DailyEntry(models.Model):
date = models.DateField(default=datetime.date.today)
# date_added = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
variable_name = models.CharField(max_length=50, default='')
variable_id = models.SmallIntegerField(default=0000)
entry = models.FloatField(default=9999)
class Meta:
verbose_name_plural = 'Daily Entries'
def __str__(self):
return self.variable
Form...
class VariablesForm(forms.ModelForm):
class Meta:
model = Variable
fields = ['variable', 'entry_type' ]
labels = {'variable':'Dependent variable to track', 'entry_type': 'Type of measure'}
class DailyEntryForm(forms.ModelForm):
class Meta:
model = DailyEntry
fields = ['variable_name', 'variable_id', 'entry', 'date']
labels = {'entry': 'Daily entry', 'date': 'Date'}
widgets = {'variable_name': forms.HiddenInput(), 'variable_id': forms.HiddenInput()}
Views...
def daily_entry(request):
''' page to make daily entries '''
vars = Variable.objects.filter(id__gt = 0 )
if request.method != 'POST':
# No data submitted. GET submitted. Create a blank form
form = DailyEntryForm()
else:
#POST data submitted. Process data
form = DailyEntryForm(data=request.POST)
if form.is_valid():
data = form.save(commit=False)
data.created_by = request.user
data.save()
return HttpResponseRedirect(reverse('entry_new'))
context = {'form': form, 'vars': vars}
return render(request, 'entry_new.html', context)
and HTML...
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
{% for var in vars %}
<div>
<ul>
<h3>{{ var.variable }}</h3>
<form class="" action="" method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="hidden" name="variable_id" value="{{ var.id }}" >
<input type="hidden" name="variable_name" value="{{ var.variable }}">
<input type="submit" name="" value="Save" />
</ul>
</div>
{% endfor %}
{% endblock content %}
Any help, well, helps...
Thanks!
How can I set the name of the form with model form ?
This is my model form :
class DetayModelForm(forms.ModelForm):
class Meta:
model = Detay
fields = [ 'yazi', 'tip', 'kullanimAdet']
I know how to set name attribute of a field in a form, there are a lot of examples also.
But I really really couldn't find that how can I set the form's own name , not a field in the form, exactly form's own name attribute; in the ModelForm class.
In html side , I will use this attribute : https://www.w3schools.com/tags/att_form_name.asp
I need to use html form name attribute in my template but, I couldn't find how to add this attribute to the form directly in ModelForm class.
I tried to use init in ModelForm class like that :
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = 'DetayFormu'
But in html side, still form doesn't have a name attribute.
And also I know , yes I can set this attribute in my template like that :
<form method="post" name="DetayFormu">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">Sign in</button>
</form>
But I really wonder how can I set this attribute in ModelForm class
directly.
How can I do this ?
Just use helper attr to add any new attributes
from crispy_forms.helper import FormHelper
class DetayModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_action = 'url'
super(DetayModelForm, self).__init__(*args, **kwargs)
self.helper.attrs = {'name': 'DetayFormu','autocomplete':'off'}
class Meta:
model = Detay
fields = [ 'yazi', 'tip', 'kullanimAdet']
The Problem
I'm trying to make a project, where you can make topics, that can be private or public to unauthenticated users. In every topic, you can then make several entries, applying to that topic. Now I'm trying to make a checkbox in my new_topic.html, where if you check it, it evaluates to True, if not, to False. But I'm having trouble with making the checkbox evaluate to True, when I check it. And for some reason, there is two checkbuttons in the page that applies to new_topic.html; one with a label, and one with just a box.
What I've tried
I've tried recreating the db.sqlite3 database. But that didn't work.
I've tried using request.POST.get('public', False) in my new_topic() function, when saving the new_topic variable.¨
The Code
My learning_logs/models.py looks like this:
from django.db import models
from django.contrib.auth.models import User
class Topic(models.Model):
"""A topic the user is learning about."""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
public = models.BooleanField(default=False)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
"""Return a string representation of the model."""
return self.text
class Entry(models.Model):
"""Something specific learned about a topic."""
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model."""
# Add an ellipsis ONLY if the entry,
# is more than 50 characters long.
if self.text > self.text[:50]:
return self.text[:50] + "..."
elif self.text <= self.text[:50]:
return self.text[:50]
My learning_logs\views.py looks like this:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect, Http404
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from .models import Topic, Entry
from .forms import TopicForm, EntryForm
def index(request):
"""The Home Page for Learning Log."""
return render(request, 'learning_logs/index.html')
def check_topic_owner(request, topic):
"""Checks if the topic requested, is requested by the owner.
Else return Http404.
"""
if topic.owner != request.user:
raise Http404
#login_required
def topics(request):
"""Show all topics."""
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
#login_required
def topic(request, topic_id):
"""Show a single topic and all its entries."""
topic = get_object_or_404(Topic, id=topic_id)
# Make sure the Topic belongs to the current user.
check_topic_owner(request, topic)
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
#login_required
def new_topic(request):
"""Add a new topic."""
if request.method != 'POST':
# No data submitted; create a blank form.
form = TopicForm()
else:
# POST data submitted; process data.
form = TopicForm(data=request.POST)
if form.is_valid():
new_topic = form.save(commit=False)
new_topic.owner = request.user
new_topic.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
context = {'form': form}
return render(request, 'learning_logs/new_topic.html', context)
#login_required
def new_entry(request, topic_id):
"""Add a new entry for the particular topic."""
topic = get_object_or_404(Topic, id=topic_id)
check_topic_owner(request, topic)
if request.method != 'POST':
# No data submitted; create a blank form.
form = EntryForm()
else:
# POST data submitted; process data.
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
if new_entry.topic.owner == request.user:
new_entry.save()
else:
return Http404
return HttpResponseRedirect(reverse('learning_logs:topic',
args=[topic_id]))
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
#login_required
def edit_entry(request, entry_id):
"""Edit an existing entry."""
entry = get_object_or_404(Entry, id=entry_id)
topic = entry.topic
check_topic_owner(request, topic)
if request.method != 'POST':
# Initial request; pre-fill form with the current entry.
form = EntryForm(instance=entry)
else:
# POST data submitted; process data.
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topic',
args=[topic.id]))
context = {'entry': entry, 'topic': topic, 'form': form}
return render(request, 'learning_logs/edit_entry.html', context)
My learning_logs\forms.py looks like this:
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}
My learning_logs\templates\learning_logs\new_topic.html looks like this:
{% extends "learning_logs/base.html" %}
{% load bootstrap3 %}
{% block header %}
<h2>New topic:</h2>
{% endblock header %}
{% block content %}
<h1>Add a new topic:</h1>
<form action="{% url 'learning_logs:new_topic' %}" method='post'
class="form">
{% csrf_token %}
{% bootstrap_form form %}
<div class="form-check">
<input class="form-check-input" type="checkbox" value=True id="public">
<label class="form-check-label" for="public">
Make it public?
</label>
</input>
</div>
{% buttons %}
<button name="submit" class="btn btn-primary">Add Topic</button>
{% endbuttons %}
</form>
{% endblock content %}
I just can't seem to fix this error. Thanks in advance! Any help is appreciated.
You have:
<input class="form-check-input" type="checkbox" value=True id="public">
<label class="form-check-label" for="public">
Make it public?
</label>
</input> # <<< this closing tag is wrong
correct input tag :
<input class="form-check-input" type="checkbox" value=True id="public" />
So you'll have:
<label class="form-check-label" for="public">
Make it public?
</label>
<input class="form-check-input" type="checkbox" value=True id="public" />