I have a Django form field, for a time, where I set the initial value to the current time by using the time.strfrtime() function.
Using this method, the field is initially populated correctly with the current time when I first load the Django app (by executing python manage.py runserver). However, subsequently if I reload the page, the form field fails to update the current time, instead repeatedly spitting back the time the app was first started. However, if I modify one of my Django files, such as forms.py or views.py, then the current time is correctly refreshed.
In forms.py:
class EventForm(ModelForm):
end_time = forms.TimeField(label="Current time", initial=strftime("%I:%M %p"))
In views.py:
def index(request):
event_form = EventForm()
How do I get the end_time field's initial value to always update to the current time?
Note: I'm aware I can fix this by passing in initial data to my event_form = EventForm() code, such as:
event_form = EventForm(initial={'end_time':strftime("%I:%M %p")})
But is there a better way? What's going on behind the scenes that the current time won't update automatically itself?
You need to set it in __init__() method of the class.
from time import strftime
from django import forms
class EventForm(forms.Form):
end_time = forms.TimeField(label='Current time')
def __init__(self, *args, **kwargs):
kwargs.update(initial={
'end_time': strftime('%I:%M %p')
})
super(EventForm, self).__init__(*args, **kwargs)
Related
I have a localized Django App, localisation works well and the configuration is ok…
For forms needs, I use __unicode__ method for model to render ModelChoiceFields, but how do I format localized date in the unicode return?
In this method I have no access to current timezone, how to display my TimeSpanList correctly to my users? Currently, it displays UTC. I tryed django.template.defaultfilters.date and Simon Charette's django.utils.formats.localize which didn't helped as they probably lacked contextual data…
class TimeSpan(models.Model):
start = models.DateTimeField(_("start"))
end = models.DateTimeField(_("end"))
def __unicode__(self):
return u"from UTC:{0} to UTC:{1}".format(self.start, self.end)
class TimeSpanChooserForm(forms.Form):
time_span = forms.ModelChoiceField(
label=_("time span"), queryset=TimeSpan.objects.all())
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(TimeSpanChooserForm, self).__init__(*args, **kwargs)
How to know current locale without current request object to localize those datetimes? (If there is a way)
note: to me __unicode__ seems like the only way to display entries in ModelChoiceField.
note 2: to me, Yuji 'Tomita' Tomita comment is the best current answer but it lacks a usable exemple…
Thanks for reminding me about this question. It's a bit involved for a complete answer.
I've created a demo method that shows this in action. Run test() to see output.
The remaining challenge is getting the client timezone. This will probably be done with some JavaScript that adds a parameter to your GET/POST or adds a custom header to be read in the view.
Since this is an unknown, I just stubbed it out with a method that returns a random time zone. You should update it to reflect your client timezone retrieval method.
Here's a self documenting example:
Models
from django.utils.formats import localize
class TimeSpan(models.Model):
start = models.DateTimeField("start")
end = models.DateTimeField("end")
def __unicode__(self):
return u"from UTC:{0} to UTC:{1}".format(self.start, self.end)
Form
from django import forms
class TimeSpanChooserForm(forms.Form):
time_span = forms.ModelChoiceField(
label=("time span"), queryset=TimeSpan.objects.all())
def __init__(self, *args, **kwargs):
# get request and TZ from view. This is a special form..
self.request = kwargs.pop("request", None)
self.tz = kwargs.pop('tz', None)
super(TimeSpanChooserForm, self).__init__(*args, **kwargs)
# modify the choices attribute to add custom labels.
self.fields['time_span'].choices = [
self.make_tz_aware_choice(self.request, timespan) for timespan in self.fields['time_span'].queryset
]
def make_tz_aware_choice(self, request, timespan):
""" Generate a TZ aware choice tuple.
Must return (value, label). In the case of a ModelChoiceField, (instance id, label).
"""
start = timespan.start.replace(tzinfo=self.tz)
end = timespan.end.replace(tzinfo=self.tz)
label = "From {tz} {start} to: {tz} {end}".format(
start=start,
end=end,
tz=self.tz,
)
return (timespan.id, label)
View
import random
import pytz
from django import http
from django import template
def view(request):
""" Render a form with overridden choices respecting TZ
"""
def get_tz_from_request(request):
""" Get the client timezone from request.
How you do this is up to you. Likely from JS passed as a parmeter.
"""
random_timezone = random.choice(pytz.all_timezones)
return pytz.timezone(random_timezone)
form = TimeSpanChooserForm(request.POST or None, request=request, tz=get_tz_from_request(request))
ctx = template.Context({
'form': form,
})
rendered_template = template.Template("{{ form }}").render(ctx)
return http.HttpResponse(rendered_template)
def test():
from django.test import RequestFactory
rf = RequestFactory()
r = rf.get('/')
for i in range(10):
print str(view(r))
You can use the django.utils.formats.localize function.
from django.db import models
from django.utils.formats import localize
from django.utils.translation import ugettext
class TimeSpan(models.Model):
start = models.DateTimeField(_('start'))
end = models.DateTimeField(_('end'))
def __unicode__(self):
return ugettext("from %(start)s to %(end)s") % {
'start': localize(self.start),
'end': localize(self.end),
}
You can test the following work with those manipulation.
from django.utils import timezone
from django.utils.translation import override
with override('fr'):
print(TimeSpan(start=timezone.now(), end=timezone.now()))
with override('en'):
print(TimeSpan(start=timezone.now(), end=timezone.now()))
Both should display different formating.
If you want to make sure dates are displayed in a specific timezone you must make sure USE_TZ = True in your settings and set the TIME_ZONE setting to the one you want to use. You can set this timezone per request by using a middleware that calls django.utils.timezone.activate with the desired timezone.
You can also use the django-sundial package which takes care of this for you.
Try this:
def __unicode__(self):
return u'%s %s'%(self.start,self.end)
Buy using this it will return combined start and end datetime objects.This worked for me
I am trying to create a wizard to import several of a model in Django using JSON. I want to be able to:
go to a URL like entries/import/ which will display a textfield
where I can paste in some JSON entries and hit submit
which will then bring me to step 2 of the wizard where I will be presented with a list of model forms
where upon inspecting/changing some data I can hit submit and have all the entries saved
It looks like I want to use a Form Wizard in conjunction with a FormSet. I have steps 1 and 2 complete, but I can't figure out how to get all the models to be presented as forms on step 2 of the wizard.
I've come across this link that shows where I might be able to convert the JSON to a FormSet, but I haven't been able to get it to work as of yet. Below is what I believe to be the relevant code. Can you help me figure out how to get the formset to be passed to step2?
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text', 'tags']
class ImportForm(forms.Form):
json = forms.CharField(widget=forms.Textarea, label='JSON')
class ImportSelectionForm(forms.Form):
entryFormSet = formset_factory(EntryForm)
FORMS = (
("step1", ImportForm),
("step2", ImportSelectionForm),
)
TEMPLATES = {
"step1": "entries/json_form.html",
"step2": "entries/entry_import_form.html",
}
class ImportWizard(SessionWizardView):
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def get_form_initial(self, step):
current_step = self.storage.current_step
if current_step == 'step2':
# Not getting here for some reason after submitting step1
prev_data = self.storage.get_step_data('step1')
json = prev_data.get('step1-json', '')
models = serializers.deserialize("json", json)
EntryFormSet = formset_factory(EntryForm)
formset = EntryFormSet(self.request.POST, self.request.FILES)
return self.initial_dict.get(step, {'formset': formset})
return self.initial_dict.get(step, {})
def done(self, form_list, **kwargs):
return HttpResponseRedirect(revierse_lazy('entries:index'))
You should just use step parameter passed to get_form_initial method instead of using self.storage.current_step. After testing I've noticed that self.storage.current_step contains previous step.
Also notice that for some reason get_form_initial is processed twice, once for previous and once for current step.
I have a model that saves an Excursion. The user can change this excursion, but I need to know what the excursion was before he change it, because I keep track of how many "bookings" are made per excursion, and if you change your excursion, I need to remove one booking from the previous excursion.
Im not entirely sure how this should be done.
Im guessing you use a signal for this?
Should I use pre_save, pre_init or what would be the best for this?
pre_save is not the correct one it seems, as it prints the new values, not the "old value" as I expected
#receiver(pre_save, sender=Delegate)
def my_callback(sender, instance, *args, **kwargs):
print instance.excursion
Do you have several options.
First one is to overwrite save method:
#Delegate
def save(self, *args, **kwargs):
if self.pk:
previous_excursion = Delegate.objects.get(self.pk).excursion
super(Model, self).save(*args, **kwargs)
if self.pk and self.excursion != previous_excursion:
#change booking
Second one is binding function to post save signal + django model utils field tracker:
#receiver(post_save, sender=Delegate)
def create_change_booking(sender,instance, signal, created, **kwargs):
if created:
previous_excursion = get it from django model utils field tracker
#change booking
And another solution is in pre_save as you are running:
#receiver(pre_save, sender=Delegate)
def my_callback(sender, instance, *args, **kwargs):
previous_excursion = Delegate.objects.get(self.pk).excursion
if instance.pk and instance.excursion != previous_excursion:
#change booking
You can use django model utils to track django model fields. check this example.
pip install django-model-utils
Then you can define your model and use fieldtracker in your model .
from django.db import models
from model_utils import FieldTracker
class Post(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
tracker = FieldTracker()
status = models.CharField(choices=STATUS, default=STATUS.draft, max_length=20)
after that in post save you can use like this :
#receiver(post_save, sender=Post)
def my_callback(sender, instance,*args, **kwargs):
print (instance.title)
print (instance.tracker.previous('title'))
print (instance.status)
print (instance.tracker.previous('status'))
This will help you a lot to do activity on status change. as because overwrite save method is not good idea.
As an alternative and if you are using Django forms:
The to-be version of your instance is stored in form.instance of the Django form of your model. On save, validations are run and this new version is applied to the model and then the model is saved.
Meaning that you can check differences between the new and the old version by comparing form.instance to the current model.
This is what happens when the Django Admin's save_model method is called. (See contrib/admin/options.py)
If you can make use of Django forms, this is the most Djangothic way to go, I'd say.
This is the essence on using the Django form for handling data changes:
form = ModelForm(request.POST, request.FILES, instance=obj)
new_object = form.instance # not saved yet
# changes are stored in form.changed_data
new_saved_object = form.save()
form.changed_data will contain the changed fields which means that it is empty if there are no changes.
There's yet another option:
Django's documentation has an example showing exactly how you could do this by overriding model methods.
In short:
override Model.from_db() to add a dynamic attribute containing the original values
override the Model.save() method to compare the new values against the originals
This has the advantage that it does not require an additional database query.
I am using django-haystack with Solr backend in one of my projects. I have a SearchForm which inherits from FacetedSearchForm. Now, what I am trying to do is to add an intital value to some of the fields within the Form.
from django.forms import forms
from haystack.forms import FacetedSearchForm
MySearchForm(FacetedSearchForm):
""" My basic search form
"""
field_one = forms.CharField(required=False, initial='0')
field_two = forms.CharField(required=False, initial='some_value')
previously when i was using django.forms.Form this used to work absolutely fine. I have been digging around django code and haystack code and fond that FacetedSearchForm extends haystack.forms.SearchForm which extends django.forms.Forms. So I can not see a reason why this should not work.
References:
haystack.forms.FacetedSearchForm
django.forms.Form
I also tried overriding the __init__ method and thought I would set the initial values there:
def __init__(self, *args. **kwargs):
data = kwargs.pop('data', None)
if data:
values = {
'field_one': (data.get('field_one', None), '0'),
'field_two': (data.get('field_two', None), 'some_value')
}
for value in values:
if not values[value][0]:
self.fields[value].initial = values[value][3]
super(MySearchForm, self).__init__(*args, **kwargs)
but in a FacetedSearchForm there is no fields attribute, though they provide access to base_fields object, so I instead tried doing:
self.base_fields[value].initial = values[value][4]
which does not work either.
Strange part came when I realized that It does not raise any errors or exceptions, so I tried printing out the initial values to debug and well it does show that inital values are set.
But when the form is rendered in the template the fields does not have those initial values. The values for the fields are None for both fields in the template.
I tried checking the value using:
{{ form.field_one.value }}
{{ form.field_two.value }}
and the output is:
None
None
Have anyone ran into this issue before ? How can I have inital values show up in the template when the form is rendered ?
As django.forms.Form set initial data only to an unbound form setting inital is not an option here. We should rather pass the inital values as data to the build_form method in haystack.views.FacetedSearchView as this is where form is initialized.
Example:
MySearchView(FacetedSearchView):
def build_form(self, form_kwargs=None):
""" Overrides the build_form method to send initial data
"""
form = super(MySearchForm, self).build_form(form_kwargs)
form_data = form.data.copy() # Need to copy this as QueryDict is immutable
# Set initial data hete
form_data['field_name'] = 'fields_inital_value'
form.data = form_data
return form
I have this form.py:
from shortcut import get_timezone
class HostCreateForm(forms.ModelForm):
def save(self, commit=True):
host1 = super(HostCreateForm, self).save(commit=False)
user = request.user.username
host1.timezone=get_timezone(user=user)
host1.save()
return host1
class Meta:
model = Host
widgets = {
'user': forms.HiddenInput(),
'timezone': forms.HiddenInput()
}
timezone is an input hidden field from Host model defined in model.py.
get_timezone method: (definition as following)
def get_timezone(user=None):
timezone = Userprofile.objects.all()
timezone = timezone.get(user=user)
return timezone.timezone
whereas UserProfile is another model also defined in models.py which is used to store the timezone , first name and last name of a user at the time of his/her registration. My main motive is to get this default timezone from Userprofile model and pass it to that hidden input field (timezone).
I tried with the above code (host1.timezone=get_timezone(user=user) written in save method which call the get_timezone method and it should return the timezone form Userprofile model for a loginned user but it didn't work.
I think the main problem are coming with the passing the name of requesting user at the time of calling get_timezone methos from save method. I am not able to do that. From the current scenario i am getting the following error:
global name 'request' is not defined
Any help will be appreciable
timezone = timezone.filter(user=user)
return timezone.timezone
timezone.filter(...) will return you a queryset, and you can not call .timezone on a queryset. You must get the related object by either using get:
timezone = timezone.get(user=user)
or by list slicing:
timezone = timezone.filter(user=user)[0]
UPDATE: Ok, i just notice it.
Problem is, you can not pass a request object to a form's save method. Best way to deal with the problem is doing it in your view layer.
In your view:
my_form = HostCreateForm(request.POST):
if my_form.is_valid(): #do the validation
my_inst = my_form.save(commit=False) #pass the form values to a new model instance without saving.
my_inst.timezone = request.user.username #set the user or do whhatever you want eith the form data.
my_inst.save() #that will call your save methhod that you override.
And you remove the parts that sets the user from your save method.