Unable to use DateTime input through django CreateView and ModelForm - django-forms

I tried to create an object of Model Event through UI using CreateView
My Model is
class Event(models.Model):
start = models.DateTimeField(_("start"), db_index=True)
end = models.DateTimeField(_("end"), db_index=True, help_text=_("The end time must be later than the start time."))
title = models.CharField(_("title"), max_length=255)
description = models.TextField(_("description"), blank=True)
rule = models.ForeignKey(Rule)
calendar = models.ForeignKey(Calendar)
My ModelForm is
class EventForm(forms.ModelForm):
class Meta:
model=Event
fields=['start','end','title','description','rule','calendar',]
My url is
url(r'^addEvent/$', CreateEventView.as_view(), name='add-event'),
View is
class CreateEventView(CreateView):
form_class=EventForm
template_name="createEventForm.html"
success_url='/eventListView/'
I have tried to automatically render this in the following HTML template
{% extends "base.html" %}
{% load i18n %}
{% block body %}
<form method='POST'>{% csrf_token %}
{{form.as_p}}
<button type="submit">Save</button>
</form>
<h2></h2>
{% if error %}
<p>Error {{error}}</p>
{% endif %}
{% endblock %}
The other components are rendering well, including foreign-key as dropdown. I am facing a problem only with the date-time fields start and end, which are rendering as text-fields.
I tried many solutions, including adding init function to my form after removing the datetime fields
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
self.fields['start']=forms.DateTimeField(widget=forms.widgets.DateTimeInput())
But they are still rendering as text-fields.
I used another method suggested online and added
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdn.bootcss.com/bootstrap-datetimepicker/4.17.44/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="//cdn.bootcss.com/moment.js/2.17.1/moment.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap-datetimepicker/4.17.44/js/bootstrap-datetimepicker.min.js"></script>-->
<script>
$(function () {
$('.datetime-input').datetimepicker({
format:'YYYY-MM-DD HH:mm'
});
});
</script>
To my base template and modified the form object to include widgets
class EventForm(forms.ModelForm):
class Meta:
model=Event
fields=['start','end','title','description','rule','calendar',]
widgets = {
'start': forms.widgets.DateTimeInput(attrs={'class':'datetime-input'}),
}
Even though the 'start' field is recognising the format on the datepicker function from bootstrap-datepicker, it is still rendering it as a text field.
I got the following error on the browser console:
Uncaught Error: datetimepicker component should be placed within a relative positioned container
Please let me know if there is a way to get the datetime-widget to display on the UI. Thank you. Sorry if you found my approach amateurish.

Related

Django crispy-forms doesn't show error meesage for FileExtensionValidator

Package version:1.7.2
Django version:2.1
Python version:3.7
Template pack: Bootstrap4
I have a FileField in my model and I implemented Django's FileExtensionValidator, as well as my own custom field validator to check the file size. It works, but crispy-forms doesn't display error message when these validations fail.
Model
from django.core.validators import FileExtensionValidator
class Project(models.Model):
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='projects',
)
title = models.CharField(
_('project name'),
max_length=100,
help_text=_('Required. 100 characters or fewer.'),
)
slug = models.SlugField(
_('slug'),
max_length=80,
)
created = models.DateTimeField(
_('dateTime created'),
auto_now_add=True,
)
xmlfile = models.FileField(
_('input file'),
upload_to=user_directory_path,
validators=[FileExtensionValidator(allowed_extensions=('xml',))],
help_text=_('Required. Please upload an XML file.'),
)
Form
from django.core.exceptions import ValidationError
def file_size(value):
limit = 9 * 1024 * 1024
if value.size > limit:
raise ValidationError('File too large. Size should not exceed 9 MiB.')
class ProjectForm(forms.ModelForm):
xmlfile = forms.FileField(
label='XML File Upload',
widget=forms.FileInput(attrs={'accept':'application/xml'}),
validators=[file_size],
)
class Meta:
model = Project
widgets = {
'owner': HiddenInput(),
}
Template
{% block content %}
<h1>New Project</h1>
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy }}
<input type="submit" value="Create" />
</form>
{% endblock content %}
View
class ProjectCreate(CreateView):
form_class = ProjectForm
model = Project
template_name = 'project_new.html'
success_url = reverse_lazy('my_projects_list')
def get_initial(self):
initial = super().get_initial()
initial['owner'] = self.request.user
return initial
When trying to upload an XML file less than 9M, it works and the user is brought to the success URL. But when either file format or file size is wrong, it's correct that we continue to stay on the page of project_new.html, but no error message is displayed on this page related to FileExtensionValidator or file_size().
When I change {{ form|crispy }} to {{ form.as_p }}, the validation error will be displayed on the screen. Do you know how to display validation error messages when using {{ form|crispy }}? Thank you!
According to crispy docs:
'By default when django-crispy-forms encounters errors, it fails silently, logs them and continues working if possible. A settings variable called CRISPY_FAIL_SILENTLY has been added so that you can control this behavior. If you want to raise exceptions instead of logging, telling you what’s going on when you are developing in debug mode, you can set it to:
CRISPY_FAIL_SILENTLY = not DEBUG
Besides you can check other error attributes here(documentation):
https://django-crispy-forms.readthedocs.io/en/d-0/tags.html#helper-attributes-you-can-set

Display something upon a change to a BooleanField in a django database in django app

I have an uber like django app. A customer can signal that they are looking for someone to provide a service and people in the same area who are looking to be service providers can view a list of actively seeking customers. When the customer presses the button signaling they are looking for a service, they are redirected to a page that says "Thank you, you will be notified when someone is on the way" and it sets attribute active to True. This displays them on the previously mentioned list where customers can be 'claimed.' When a service provider claims a customer. I want the customer's page to display 'Someone will be there shortly" or something of that nature. But how can I let the customer's page (customer_active.html) know that active has been set to False(ie they have been claimed) and then display a message on the occurrence of that event? I have read about potentially using django signals or ajax/jquery but I do not know what the right route is nor how to implement the solution in said route. I have the following code:
models.py:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
active = models.BooleanField(default = False)
urls.py
urlpatterns = [
#home
url(r'^home/$', views.home, name = 'home'),
#/claim/user_id
url(r'^claim/(?P<user_id>[0-9]+)/$', views.ClaimView.as_view(), name = "claim"),
#/active
url(r'^active/$', views.customer_active, name='customer_active'),
]
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
customer_active.html (the customer is looking at this after they signal they are looking for the service):
{% extends 'core/base.html' %}
{% block head %}
{% load static %}
<link rel="stylesheet" href="{% static 'core/customer_active.css' %}">
<title>Active</title>
{% endblock %}
{% block body %}
<div class="container text-center">
<div class="row">
<div class="col-md-12 mb-3">
<h1 class="lead">Thank you, {{ user.first_name}} {{user.last_name }}, you will be alerted
when someone claims your laundry</h1>
</div>
</div>
</div>
{% endblock %}
home.html:
{% for customer in customers%}
<tr>
<td style = "text-align:center">{{ customer.user.first_name|title }} {{ customer.user.last_name|title }}</td>
<td style = "text-align:center">{{ customer.user.profile.address|title }}</td>
<td style = "text-align:center">Claim</td>
</tr>
{% endfor %}
views.py:
#the service provider that claimed a customer gets redirected to 'claim.html', upon this 'active' gets set to False
class ClaimView(View):
def get(self, request, user_id, *args, **kwargs):
customer = User.objects.get(id=user_id)
customer.profile.active = False
customer.save()
return render(request, 'core/claim.html', {'customer': customer})
def customer_active(request):
request.user.profile.active = True;
request.user.save()
return render(request, 'core/customer_active.html', {'user': request.user})
How can I use ajax/jquery to display a message saying 'Someone will be there shortly" in customer_active.html upon a service provider claiming the customer?
So.. if a customer is in customer_active.html page, when service provider claims the customer, the customer should be alerted in customer_active.html page. If I have perceived the problem correctly, then it is a matter of creating a timer that will send Ajax calls every few seconds to check if a service provider has claimed the customer.
setInterval(function(){
minAjax({
url:"/serviceprovider/",//request URL
type:"GET",//Request type GET/POST
//CALLBACK FUNCTION with RESPONSE as argument
success: function(data){
//check if someone has claimed
if (data == true){
alert("Someone will be there shortly");
}
}
});
}, 5000); //5000 represents 5 seconds
Views.py
#login_required
def serviceprovider(request):
claimed=False
claim=Model.objects.filter(user=User.objects.get(username=request.user), claimed=True)
if claim:
claimed=True
return HttpResponse(json.dumps(claimed))
urls.py
url(r'^serviceprovider/', views.serviceprovider, name = 'serviceprovider'),
You need minAjax for this to work, it is a very small library using pure javascript. Just download the library and put it in your js folder, and import it. I haven't tested the above example but I have used this method in a project and it works. This is just to guide you to the right direction. Good luck!

Use AJAX to update django if-else template

In template I have:
{% for item in items %}
{% if item.public is True%}
<a href="{% url 'motifapp:article_public_edit' article.id %}">
show icon1
</a>
{% else %}
<a href="{% url 'motifapp:article_public_edit' article.id %}">
show icon2
</a>
{% endif %}
{endfor}
I use ajax to handle the request here:
$(anchor).click(function(e){
e.preventDefault();
var href = $(this).attr('href')
$.ajax({
type: 'POST',
url: href,
data: {csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),},
success: function (response) {
//refresh a div ?
}
});
});
Ajax handle the post, and in view it reverse a boolean value 'public'.
Question is, how do I update the template? I can pass the opposite icons and value, but it seems very redundant. Is there a way just to refresh that div with anchor and let the if else statement takes care of what's showing ?
Thanks
You are 99% close to answer your own question.
First, you you must wrap the {% for %} in a div and save it as a separate file, say:
<!-- injection.html -->
<div id="some-id>
{% for item in items %}
same code here
{% endfor %}
</div>
Now in your main template you should include that file like this:
<div id="wrapper">
{% include 'templates/injection.html' %}
</div>
Now, once you make an AJAX request to your views function then this view should render that div (which is a separate html file) but with a different items value. Like this:
# views.py
def article_public_edit(request, id):
if request.is_ajax():
# new calculation of the items QuerySet
# depending on the data passed through the $.ajax
return render(request, 'injection.html', locals())
Finally, you can do this inside the $.ajax() success function:
$.ajax({
type: 'POST',
url: href,
data: {csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),},
success: function (response) {
// replace the inside of #wrapper div with the injection.html (that has the new items values)
$("#wrapper").replaceWith(response);
}
});
With ajax calls there is no way the Django template tags will be re-evaluated. They only get evaluated on initial page load.
I think the best way to go is to have the ajax view return the boolean value and then set the image in your front-end code depending on that value.

How to load Django new dynamic content in a Jquery Dialog?

Im trying to do what is suggested here: How to reopen a Django form in a jQuery dialog when the form validation fails in the backend?
But I don't have enough points to add a comment there..
In my base html page i have a link which opens a dialog with a Django-form. I use the jquery load() to fill the Dialog with this child-html-template. In this child template i have a submit button. I'm trying to bind this button to an ajax function that will:
Post the form to the right URL
Fetch the response from Django view (the form as HTML to be able to show valdidation errors)
Replace the content in the dialog box with the data i get back from the submit-POST.
Is this possbible? Been working on this for days now and i just cant make it happen. Can somone post an example with code to end my suffering.. It's the ajax that is my biggest problem.
Where should i put the script? In the base or the child template? Do you have any alternative solutions?
Thank you!
I did this not long ago in. I found it easier to send the errors in json, and then handle them client-side and attach them to the relevent fields. Like so:
Use ajax to load the form from a view into the jQuery dialog box
When the user sends the data send the information to same view
If validation fails, send errors as a json array. Use js on client-side to attach them to the relevant fields
If succeeds send a positive response of some kind
Check out this excellent example for reference
edit
Here's a working example. Not sure I'm using the best methods to do this, but I think it's pretty understandable. Also, I'm not accounting for the possibility of non-ajax form submit (it's easy enough to do, some logical conditioning using form.is_ajax() and see example linked above for further reference).
So first the views (ContactForm is the same as the one linked):
import json
from django.http import HttpResponse
from django.shortcuts import render_to_response
def home(request):
return render_to_response('index.html') #nothing special here
from django.views.decorators.csrf import csrf_exempt
from cStringIO import StringIO
#csrf_exempt #you should use csrf, I'm just skipping this for the example
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
response = {}
if form.is_valid():
response["status"] = "OK"
# save the data, or do whatever.
else:
response["status"] = "bad"
response.update(form.errors)
# now just to serialize and respond
s = StringIO()
json.dump(response, s)
s.seek(0)
return HttpResponse(s.read())
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
As you can see, if there's nothing here you get an html with the form, if not, you get a json response with an object called response which contains 'status' and might also contain errors. I'm using StringIO with json.dump(data, file) as it has always proved the least buggy and most fluent way I ever used to serialize to json (seriously. You won't believe how easily it can break).
Now let's go over the client side:
base.html:
<html>
<head>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
<link rel="stylesheet" href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css">
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
contact.html:
{% extends 'base.html' %}
{% block content %}
<form method="post" id='myform'>
{{ form.as_p }}
</form>
{% endblock %}
and finally, the main.html with the relevant js. This is where the magic happens:
{% extends 'base.html' %}
{% block content %}
<button class='btn'>Contact!</button>
<div id="dialog-modal">
</div>
<script>
$(function() {
$('button').on('click', function() {
// first things firts, fetch the form into the dialog
$('#dialog-modal').load('contact/ #myform');
// initiate dialog
$('#dialog-modal').dialog({
height: 450,
modal: true,
// I'm using the built-in buttons, but you can use your own
buttons: {
Send: function() {
var dialog = $(this),
form = $('#myform'),
data = form.serialize();
$('.off').remove(); // this is to avoid duplicates
// run ajax post call
$.ajax({
url: 'contact/',
data: data,
type: 'post',
// if successful print response
success: function(response) {
res = $.parseJSON(response);
// if form was successful:
if (res['status'] == 'OK') {
// form was successful
alert('Thank you! Form has been submitted'); // say thank you
dialog.dialog('close'); // close dialog
}
// if not...
else if (res['status'] == 'bad') {
delete res['status'] // we don't need this anymore...
var errors = res; // just for simplicity
$.each(errors, function(key, value) {
// i.e. key='subject' and value is the error message itself
var err = $('<span></span>', {
'class': 'off',
'text': value
}),
br = $('<br></br>', {
'class': 'off',
}),
input = $('#id_'+key).parent(); //find the parent div for the relevant input by key
// add a small break
br.appendTo(input);
// add the error
err.appendTo(input);
// add some styling
err.css('color', 'red').css('font-size', '10px');
});
}
}
});
}
}
});
});
});
</script>
{% endblock %}
Hope that's not too much. Here's an image how it looks after attempting to send:
Pick it up from here. There's a lot of room to play around and extend this.
Good luck!

django-crispy-forms : help_text_inline of FormHelper not working as expected

For the following model:
class MyModel(models.Model):
name = models.CharField(max_length=110, help_text="Some sample help text.")
def __unicode__(self):
return u'%s' % (self.name)
And the following modelform:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-myModelForm'
self.helper.form_class = 'form-horizontal'
self.helper.form_action = 'my_model_form_url'
self.helper.form_error_title = 'Form Errors'
self.helper.help_text_inline = True
class Meta:
model = MyModel
Using the following template:
{% extends "base.html" %}
{% block content %}
{% load crispy_forms_tags %}
{% crispy form %}
{% endblock %}
The help_text defined in the model does not get rendered at all. It does get rendered if I change to self.helper.help_text_inline = False instead of self.helper.help_text_inline = True, but that's not what I want.
How do I get the help_text to show with self.helper.help_text_inline = True?
The base.html is all proper with bootstrap files all included.
This is due to the fact that help_text_inline and error_text_inline cannot be set to the same value in order to work. If you set help_text_inline to True, you need to set error_text_inline to False. If you don't do it, help text messages are not displayed, in order to show form errors in case they happen.
I hadn't thought about this in detail until now. So probably the best would be to add a logging warning, telling the user to be careful with this. Maybe automatically overriding the default behavior of the other flag, in case one is set. I'm open to suggestions.

Resources