When I'm trying to remove a row, the formset send me the following error [{}, {}, {}, {'id': ['This field is required']}].
If I change the following parameter can_delete=True inside the modelformset_factory, I can delete object but rows stays display even after I pressed the remove button.
I tried to add {% if form.instance.pk %}{{ form.DELETE }}{% endif %} in the template, it didn't change anything.
I am using django 2.2 and django-dynamic-formset, the country_fr/en come from modeltranslation.
Views
#login_required
def view_countries(request):
CountryFormSet = modelformset_factory(
Country,
fields=('country_fr', 'country_en'),
formset=BaseCountryFormSet,
can_delete=True)
if request.method == 'POST':
formset = CountryFormSet(request.POST)
if formset.is_valid():
formset.save()
else:
formset = CountryFormSet()
context = {
'formset': formset,
'menu': 'cards',
'menu_cards': 'countries',
'model': _('countries'),
'fields': [_('countries')+' [fr]', _('countries')+' [en]'],
}
return render(request, 'cards/index_formset.html', context)
Html
<form method="post" class="mt-2">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div id="form" class="row_formset d-flex text-center mb-1">
{% if form.instance.pk %}{{ form.DELETE }}{% endif %}
{% for field in form.hidden_fields %}
<div class="invisible">
{{ field }}
</div>
{% endfor %}
{% for field in form.visible_fields %}
<div class="flex-fill field">
{{ field }}
</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-flex mt-2 justify-content-center">
<input type="submit" value="{% trans 'submit'|capfirst %}" class="btn btn-primary"/>
</div>
</form>
Forms
class BaseCountryFormSet(BaseModelFormSet):
def clean(self):
if any(self.errors):
raise forms.ValidationError(
_('Errors : ')+f'{self.errors}'+'.',
code='unknow_error'
)
countries_fr = []
countries_en = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
country_fr = form.cleaned_data['country_fr']
country_en = form.cleaned_data['country_en']
# Check that no row have the same country_fr or country_en
if country_fr and country_en:
if country_fr in countries_fr:
duplicates = True
countries_fr.append(country_fr)
if country_en in countries_en:
duplicates = True
countries_en.append(country_en)
if duplicates:
raise forms.ValidationError(
_('Some entries are duplicated.'),
code='duplicate_row'
)
# Check that all row have both country_fr and country_en
if country_en and not country_fr:
raise forms.ValidationError(
_('Some french entries are missing.'),
code='missing_country_fr'
)
elif country_fr and not country_en:
raise forms.ValidationError(
_('Some english entries are missing.'),
code='missing_country_en'
)
Models
class Country(models.Model):
country = models.CharField(_('country'), max_length=100)
slug = models.SlugField(editable=False, max_length=100)
def save(self, *args, **kwargs):
self.slug_fr = slugify(self.country_fr, allow_unicode=False)
self.slug_en = slugify(self.country_en, allow_unicode=False)
super().save(*args, **kwargs)
So the problem come from bootstrap, the d-flex of each form of the formset contain the attribut !important wich override the display: none; of the django-dynamic-formset-script.
I made my own css class to replace d-flex. I kept can_delete=True, it's obviously useful; and I removed {% if form.instance.pk %}{{ form.DELETE }}{% endif %}.
Related
I'm having difficulty with displaying custom defined errors on a page when a user submits an invalid form submission. I set novalidate on the form element with the intent of disabling a browser's default form validation messages. At the same time the error messages that are defined on QuestionForm are not displaying on the page.
It's not clear to me why the form error messages aren't showing? Is setting novalidate causing this to occur?
class Page(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['search_form'] = SearchForm()
return context
class AskQuestionPage(Page):
template_name = "posts/ask.html"
extra_context = {
'title': "Ask a public question"
}
def attach_question_tags(self, tags):
question_tags = []
for name in tags:
try:
tag = Tag.objects.get(name=name)
except Tag.DoesNotExist:
tag = Tag.objects.create(name=name)
finally:
question_tags.append(tag)
return question_tags
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = QuestionForm
return context
def post(self, request):
context = self.get_context_data()
form = context['form'](request.POST)
if form.is_valid():
tags = self.attach_question_tags(
[tag.lower() for tag in form.cleaned_data.pop("tags")]
)
try:
question = form.save(commit=False)
question.profile = request.user.profile
question.save()
except IntegrityError:
form.add_error(None, "This post is already posted")
context['form'] = form
else:
question.tags.add(*tags)
form.save_m2m()
return SeeOtherHTTPRedirect(
reverse("posts:question", kwargs={
"question_id": question.id
})
)
return self.render_to_response(context)
<div class="question_posting_form">
<h2>{{ title }}</h2>
{% if form.non_field_errors %}
<ul class="non_field_errors">
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.errors }}
{% if form.errors %}
<ul class="non_field_errors">
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form id="postform" method="post" action="{{ request.path }}" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.html_name == "body" %}
<div class="content_posting">
{% include "./markdown_help.html" %}
<fieldset class="form_fieldset_widget">
<p>{{ field.label }}<p>
<p>{{ field.help_text }}</p>
{{ field }}
</fieldset>
<div class="post_draft" id="post_preview_question"></div>
</div>
{% elif field.html_name == "tags" %}
<fieldset id="tag_fieldset" class="form_fieldset_widget">
<p>{{ field.label }}</p>
<p>{{ field.help_text }}</p>
<div class="tag_boxes">
{% for field in field.subwidgets %}
{{ field }}
{% endfor %}
</div>
</fieldset>
{% else %}
<fieldset class="form_fieldset_widget">
<p>{{ field.label }}<p>
<p>{{ field.help_text }}</p>
{{ field }}
</fieldset>
{% endif %}
{% endfor %}
{% if request.resolver_match.url_name == "edit" %}
<button id="submit_question" type="submit">Edit Question</button>
{% else %}
<button id="submit_question" type="submit">Ask Question</button>
{% endif %}
</form>
</div>
class MultiTagWidget(MultiWidget):
def __init__(self, *args, **kwargs):
widgets = [TextInput(attrs=kwargs['attrs']) for i in range(4)]
for i, w in enumerate(widgets):
w.attrs.update({"placeholder": f"Tag {i}"})
super().__init__(widgets, *args, **kwargs)
def decompress(self, value):
if not value:
return ["", "", "", ""]
return value
class TagField(MultiValueField):
def __init__(self, *args, **kwargs):
fields = []
for i in range(4):
field = CharField(**{
"min_length": 1, "max_length": 25, "validators":[
RegexValidator("[<>`':;,.\"]", inverse_match=True)
]
})
if i == 0:
field.error_messages = {
'incomplete': "Provide at least 1 tag for your question"
}
field.required = True
else:
field.required = False
fields.append(field)
super().__init__(fields, *args, **kwargs)
def compress(self, data_list):
field_values = list(set([data.strip() for data in data_list if data]))
return field_values
class PostingForm(ModelForm):
body = CharField(
widget=Textarea(
attrs={
"class": "question_input_shade fill_block_width adjust_box"
}
), min_length=50
)
class Meta:
model = None
fields = ['body']
class QuestionForm(PostingForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['body'].help_text = "Clarify your question with as much detail as possible"
self.fields['body'].error_messages={
'required': "Elaborate on your question",
'min_length': "Add more info to your question"
}
title = CharField(
min_length=20, max_length=80,
widget=TextInput({"class": "question_input_shade fill_block_width"}),
error_messages={"max_length": "The title of your question is too long"},
help_text="Concisely describe the issue"
)
tags = TagField(
widget=MultiTagWidget(
attrs={
"min_length": 1, "max_length": 25,
"class": "question_input_shade inline_tag_input"
}
), require_all_fields=False,
help_text="Add up to 4 tags for your question"
)
Can you help me out with this. I hava a model form and I need to raise an error after validate two datetime objects in the clean method of the model form. This is what I have.
Forms
class HorariosDisponibles(forms.ModelForm):
tutor = forms.ModelChoiceField(queryset=Tutor.objects.all(),widget=forms.Select(attrs= {'class': 'input is-small is-rounded ' }),label='TUTOR',)
dia_hor_inicio =forms.DateTimeField(widget=forms.DateTimeInput(attrs= {'class': 'input is-small is-rounded ',}),label='Horario de Inicio', initial=datetime.date.today )
dia_hor_fin= forms.DateTimeField(widget=forms.DateTimeInput(attrs= {'class': 'input is-small is-rounded ' }),label='Horario de FinalizaciĆ³n', initial=datetime.date.today)
class Meta:
model = horarios_disp
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["dia_hor_inicio"].widget = DateTimeInput()
self.fields["dia_hor_inicio"].input_formats = ["%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"]
self.fields["dia_hor_fin"].widget = DateTimeInput()
self.fields["dia_hor_fin"].input_formats = ["%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"]
def clean(self):
cleaned_data = super(HorariosDisponibles, self).clean()
tutor = cleaned_data.get("tutor")
dia_hor_inicio = cleaned_data.get("dia_hor_inicio")
dia_hor_fin = cleaned_data.get("dia_hor_fin")
if dia_hor_inicio and dia_hor_fin:
if dia_hor_inicio.day != dia_hor_fin.day :
msg = 'Las fechas no pueden ser distintas'
self.add_error("dia_hor_inicio", msg)
raise forms.ValidationError("Las fechas no pueden ser distintas")
#NEITHER OF THIS APPROACHES WORKED
return cleaned_data
VIEWS
#login_required
def horario_tutor(request):
context = {
}
if request.method == 'POST':
print(request.POST)
form = HorariosDisponibles(request.POST)
if form.is_valid():
tutor = form.cleaned_data['tutor']
print("adentro")
dia_hor_inicio = form.cleaned_data['dia_hor_inicio']
dia_hor_fin = form.cleaned_data['dia_hor_fin']
tutor_horario = horarios_disp(
tutor=tutor, dia_hor_inicio=dia_hor_inicio, dia_hor_fin=dia_hor_fin)
tutor_horario.save()
context = {
'form': form
}
return redirect("home")
return render(request,"horarios_disponibles.html", context)
else:
form = HorariosDisponibles()
context['form'] = form
return render(request, "horarios_disponibles.html", context)
TEMPLATES
{% extends 'base.html' %}
{% block body %}
<section class="section">
<div class="columns is-vcentered">
<div class="column is-centered is-4 is-offset-2">
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="field">
{% for error in field.errors %}
<p class="help is-danger">{{ error }}</p>
{% endfor %}
<label for="{{field.id_for_label}}" class="label">{{ field.label }}</label>
{{ field }}
{% for non_field_error in form.non_field_errors %}
<p class="help is-danger">{{ non_field_error }}</p>
{% endfor %}
{% if field.help_text %}
<p class="help is-danger">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
<p class="control">
<button class="button is-link" type="submit">
Enviar
</button>
</p>
</form>
</section>
It validates if I put two different dates in the form, but it doesn't enter to is_valid() (because ther form is not valid). Render just the button of the template.
Try this:
if form.is_valid():
tutor = form.cleaned_data['tutor']
dia_hor_inicio = form.cleaned_data['dia_hor_inicio']
dia_hor_fin = form.cleaned_data['dia_hor_fin']
tutor_horario = horarios_disp(
tutor=tutor, dia_hor_inicio=dia_hor_inicio, dia_hor_fin=dia_hor_fin
)
tutor_horario.save()
context = {'form': form}
return redirect("home")
else:
context = {'error': 'whatever error you want to show here'}
return render(request, "horarios_disponibles.html", context)
# and probably some extra handling at the end in case there are errors
As a matter of fact, you won't need to declare the context = {} at the beginning of your code before if request.method == 'POST' because you're going to declare one on in the if-else statement anyways.
I have an Ajax Like/Dislike button that works for the product detail page. However, I want to add it on the main page, where multiple products are listed. Here is what I currently have in the models.py:
class Product(models.Model):
...
likes = models.ManyToManyField(User, blank=True, related_name='likes')
...
def total_likes(self):
return self.likes.count()
def is_liked(self, request):
return self.likes.filter(id=request.user.id).exists()
views.py:
def home(request):
products = Product.objects.all().order_by('-pub_date')
return render(request, 'product/home.html', {'product': products})
def detail(request, product_id):
product = get_object_or_404(Product, product_id=product_id)
is_liked = False
if product.likes.filter(id=request.user.id).exists():
is_liked = True
context = {
'product': product,
'is_liked': is_liked,
'total_likes': product.total_likes()
}
return render(request, 'product/detail.html', context)
def like_product(request):
product = get_object_or_404(Product, id=request.POST.get('id'))
if product.likes.filter(id=request.user.id).exists():
product.likes.remove(request.user)
is_liked = False
else:
product.likes.add(request.user)
is_liked = True
context = {
'product': product,
'is_liked': is_liked,
'total_likes': product.total_likes()
}
if request.is_ajax():
html = render_to_string('product/likes.html', context, request=request)
return JsonResponse({'form': html})
home.html:
<div class="album py-5 bg-dark">
<div class="container">
<div class="row">
{% for product in product.all %}
{% if product.featured %}
...
<div id="like-section-{{ product.id }}">{{ product.total_likes }} Like{{ product.total_likes|pluralize }}
<form action="{% url 'like_product' %}" method="post">{% csrf_token %}
{% if product.is_liked %}
<button type="submit" name="product_id" value="{{ product.id }}" class="btn btn-like btn-danger">Dislike</button>
{% else %}
<button type="submit" name="product_id" value="{{ product.id }}" class="btn btn-like btn-primary">Like</button>
{% endif %}
</form>
</div>
...
{% endif %}
{% endfor %}
</div>
</div>
</div>
Currently the status of Like/Dislike button is not being saved (When I press "Like", button changes to "Dislike" and the like is being recorded. However after I reload the page button changes back to "Like"). It seems like there is an issue in the is_liked function inside the Product class. Not sure how to fix that. Your help is much appreciated!
I've tried looking around for solutions on how to check if a form's name is already existing in the database. I used this link to figure out how, and it is indeed not allowing duplicate names to be entered. But where I expected one, I did not get an error message. I'm not sure what I'm doing wrong here, so if anyone can tell me what I should do, that would be really useful!
addgame.html:
<form method="POST" class="post-form" enctype="multipart/form-data">
{% csrf_token %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
{% endif %}
<div class="form-group">
{{ form.name.label_tag }}
{% render_field form.name class="form-control" %}
<br>
{{ form.genre.label_tag }}
{% render_field form.genre class="form-control" %}
<br>
{{ form.image.label_tag }}
{{ form.image }}
</div>
<hr>
<button type="submit" class="save btn btn-primary">Save</button>
</form>
views.py:
def addgame(request):
if request.method == "POST":
form = InfoForm(request.POST, request.FILES)
if form.is_valid():
infolist = form.save(commit=False)
infolist.created_date = timezone.now()
infolist.save()
return redirect('index')
else:
form = InfoForm()
return render(request, 'ranking/addgame.html', {'form': form})
forms.py:
class InfoForm(forms.ModelForm):
class Meta:
model = GameInfo
fields = ('name', 'image', 'genre')
def clean_name(self):
name = self.cleaned_data['name']
try:
match = GameInfo.objects.get(name=name)
except GameInfo.DoesNotExist:
return name
raise forms.ValidationError('This game has already been added to the list.')
not sure if needed, so I'll post models.py as well:
class GameInfo(models.Model):
GAME_CHOICE = [
("BMU", "Beat 'em up"),
("FT", "Fighting"),
("PF", "Platform"),
("FPS", "Shooter"),
("SV", "Survival"),
("ST", "Stealth"),
("AA", "Action Adventure"),
("EX", "Exploring"),
("SH", "Survival horror"),
("IF", "Interactive fiction"),
("IM", "Interactive movie"),
("VN", "Visual novel"),
("ARP", "Action role-playing"),
("JRP", "Japanese role-playing"),
("TRP", "Tactical role-playing"),
("CAM", "Construction and management"),
("LS", "Life simulation"),
("SP", "Sports"),
("VH", "Vehicle"),
("MOBA", "Multiplayer online battle arena"),
("RTS", "Real-time strategy"),
("RTT", "Real-time tactics"),
("TBS", "Turn-based strategy"),
("TBT", "Turn-based tactics"),
("MMORPG", "MMORPG"),
("MMOFPS", "MMO-FPS"),
("MMOR", "MMO Racing"),
("CG", "Cardgame"),
("PAC", "Point and Click"),
("MG", "Music Game"),
("VR", "Virtual Reality"),
("RC", "Racing"),
]
name = models.CharField(max_length=100)
created_date = models.DateTimeField(default=timezone.now)
image = models.ImageField(upload_to='./media/images/')
genre = models.CharField(
max_length=6,
choices=GAME_CHOICE,
default="BMU",
)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
If you are rendering the form fields manually, then it's up to you to include the errors, for example:
{{ form.name.errors }}
{{ form.name.label_tag }}
{% render_field form.name class="form-control" %}
I'm creating a page where my users can edit their articles but there is one problem, my form is not saving the modifications when I submit my form. Let's have a look:
views.py
def edit(request, id=None, template_name='article_edit.html'):
if id:
article = get_object_or_404(Article, id=id)
if article.user != request.user:
return HttpResponseForbidden()
else:
article = Article(user=request.user)
if request.POST:
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
save_it = form.save()
save_it.save()
form.save_m2m()
return HttpResponseRedirect("/")
else:
form = ArticleForm(instance=article)
context = {'form': form}
populateContext(request, context)
return render(request, template_name, context)
line 3 to 8 : Is to check if it's your own articles there is no problem with that.
line 10 to 18 : There is a problem between these lines, my form is not saving when I submit my form
forms.py (ModelForm)
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
exclude = ['date', 'rating', 'user']
form = ArticleForm()
(Line 4 : I'm excluding the date, the ratings and the username when saved so that the user can not change these informations.)
I don't have any problem in my template.html and the urls.py don't bother with that I checked over 10 times :)
Any help would be great, I'm blocked on this problem since over 1 month...
******* EDIT *******
Models.py
class Article(models.Model):
user = models.ForeignKey(User)
titre = models.CharField(max_length=100, unique=True)
summary = RichTextField(null=True, max_length=140)
contenu = RichTextField(null=True)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
image = models.ImageField(upload_to='article', default='article/amazarashi.jpeg')
rating = RatingField(can_change_vote=True)
tags = TaggableManager(through=TaggedItem, blank=True)
def __str__(self):
return self.titre
Template.html
{% block body %}
{% if user.is_authenticated %}
<p>Authentificated as <strong>{{ user.username }}</strong></p>
{% else %}
<p>NOT Authentificated</p>
{% endif %}
<h1>Edit this {{ article.titre }}</h1>
<br>
<div class="col-xs-12 col-sm-8 col-md-8 col-lg-8">
<form enctype='multipart/form-data' action="{% url "article.views.index" %}" method="post" class="form" autocomplete="off" autocorrect="off">
{% csrf_token %}
<div class="form-group">TITRE
{{ form.titre.errors }}
{{ form.titre }}
</div>
<div class="form-group">SUMMARY
{{ form.media }}
{{ form.summary.errors }}
{{ form.summary }}
</div>
<div class="form-group">CONTENU
{{ form.media }}
{{ form.contenu.errors }}
{{ form.contenu }}
</div>
<div class="form-group">
{{ form.image.errors }}
{{ form.image }}
</div>
<div class="form-group">TAGS
{{ form.tags.errors }}
{{ form.tags }}
</div>
<input type="submit" class="btn btn-default" value="Submit"/>
</div>
</form>
{% endblock %}
According to your error message.. Modify your form in templates:
<form enctype='multipart/form-data' ...>
Add request.FILES to your Form creation:
if request.POST:
form = ArticleForm(request.POST, request.FILES, instance=article)
UPDATE:
You have strange url tag parameter. Are you sure you have defined your url like: ('...', your_view, name='article.views.index')? I am in doubt cause url tag takes url name as parameter but not view path and nobody usually uses url names like this.