Prefill a DateTimeField from URL in django admin - django

How to prefill a DateTimeField from URL in django admin?
Let's say your model is :
class MyModel(models.Model):
name = models.CharField(max_length=14)
date = models.DateTimeField()
Then you can have the model "Add" form prefilled with values by passing them as GET parameters to the add view like:
/admin/app/mymodel/add/?name=Test
This is a really cool feature but how do you achieve this for a DateTimeField?
I tried many possible formats without success.
Update:
It seems impossible to do because django admin uses a SplitDateTimeWidget for DateTimeField. But if you don't mind using a different widget and loosing the datepicker, you can use a DateTimeInput widget instead.
The fastest way is to add this to your ModelAdmin class:
formfield_overrides = {
models.DateTimeField: {'widget': DateTimeInput},
}

First define the serialization/deserialization format:
DATETIME_FORMAT="%Y-%m-%d %H:%M:%S"
Then when you want to open the admin url use it:
copiedArguments = {
"fromDateTime": event.fromDateTime.strftime(DATETIME_FORMAT)
}
return HttpResponseRedirect(
u"{}?{}".format(reverse('admin:events_event_add'), urllib.urlencode(copiedArguments)))
last but not least extract the datetime in the model admin:
def get_changeform_initial_data(self, request):
initialData = super(EventAdmin, self).get_changeform_initial_data(request)
initialData["fromDateTime"] = datetime.datetime.strptime(request.GET["fromDateTime"],DATETIME_FORMAT)
return initialData

The problems seems to be with the widget. A DateField would work fine with ?date=yyyy-mm-dd, but a DateTimeField uses the SplitDateTimeWidget. It can not have a string as an initial value.
Maybe you could propose a patch to SplitDateTimeWidget to try to convert string values, or you could change the admin default widget for a DateTimeField (if that is possible).

Nowadays there's the get_changeform_initial_data ModelAdmin method.
I'm passing the query parameters like this:
?start=08/09/2022 12:30&end=08/09/2022 17:00
And it works fine by overriding the method like that:
def get_changeform_initial_data(self, request):
initial = super().get_changeform_initial_data(request)
if "end" in initial:
initial["end"] = datetime.datetime.strptime(initial["end"], "%d/%m/%Y %H:%M")
if "start" in initial:
initial["start"] = datetime.datetime.strptime(initial["start"], "%d/%m/%Y %H:%M")
return initial
It's also mentioned in this issue's comment, and useful for those facing the error 'str' object has no attribute 'utcoffset' when trying to prefill a datetime field from query parameters in Django admin.

Related

How to make a Django admin readonly textarea field

In Django admin, if I have a model field that's a TextField and set it as readonly using readonly_fields, then it's displayed as text in a <p> tag.
I'd like it to still be displayed as a textarea field, but with its disabled attribute set.
What's the simplest way to accomplish this?
use a form field
somefield = forms.CharField(
widget=forms.TextInput(attrs={'readonly':'readonly'})
)
A bit late, but here's an idea (inspired by #cinoch`s answer and this answer) that does the trick for me, with a minimum of code:
do not add the name of your TextField to the readonly_fields in your ModelAdmin subclass (otherwise step 2 has no effect)
instead, do add the following to your ModelAdmin subclass:
formfield_overrides = {
TextField: dict(widget=Textarea(attrs=dict(readonly=True)))
}
Note this requires some imports:
from django.db.models import TextField
from django.forms import Textarea
The TextField will now show up on the admin page as a scrollable Textarea instead of plain text, and its content will now be read-only, as desired.
Downside is that this applies to all TextFields in the model. If that's a problem, you should probably use a custom form as suggested by #cinoch and described in more detail here or here.
Also, this has no effect if ModelAdmin.has_change_permission() returns False.
The readonly_fields can take method names as well as field names. You could write a method that renders the value of the field in a disabled textarea.
Make sure you exclude the field from the model admin, since it will no longer be in readonly_fields.
#alasdair's answer is actually quite clever, but, unfortunately, it does not provide an example.
Here's my attempt to clarify, based on the docs for readonly_fields.
Assuming a model like so:
class MyModel(models.Model):
my_textfield = models.TextField()
The admin could look like this, using format_html to create a readonly textarea:
class MyModelAdmin(admin.ModelAdmin):
exclude = ['my_textfield']
readonly_fields = ['display_my_textfield']
#admin.display(description='my textfield')
def display_my_textfield(self, obj):
return format_html(
'<textarea cols="40" rows="10" readonly>{}</textarea>',
obj.my_textfield)
This also works if ModelAdmin.has_change_permission() returns False.

Django forms.ChoiceField without validation of selected value

Django ChoiceField "Validates that the given value exists in the list of choices."
I want a ChoiceField (so I can input choices in the view) but I don't want Django to check if the choice is in the list of choices. It's complicated to explain why but this is what I need. How would this be achieved?
You could create a custom ChoiceField and override to skip validation:
class ChoiceFieldNoValidation(ChoiceField):
def validate(self, value):
pass
I'd like to know your use case, because I really can't think of any reason why you would need this.
Edit: to test, make a form:
class TestForm(forms.Form):
choice = ChoiceFieldNoValidation(choices=[('one', 'One'), ('two', 'Two')])
Provide "invalid" data, and see if the form is still valid:
form = TestForm({'choice': 'not-a-valid-choice'})
form.is_valid() # True
Best way to do this from the looks of it is create a forms.Charfield and use a forms.Select widget. Here is an example:
from django import forms
class PurchaserChoiceForm(forms.ModelForm):
floor = forms.CharField(required=False, widget=forms.Select(choices=[]))
class Meta:
model = PurchaserChoice
fields = ['model', ]
For some reason overwriting the validator alone did not do the trick for me.
As another option, you could write your own validator
from django.core.exceptions import ValidationError
def validate_all_choices(value):
# here have your custom logic
pass
and then in your form
class MyForm(forms.Form):
my_field = forms.ChoiceField(validators=[validate_all_choices])
Edit: another option could be defining the field as a CharField but then render it manually in the template as a select with your choices. This way, it can accept everything without needing a custom validator

Assigning initial field values in bound Django admin forms

I have a fairly simple Django application (v1.3 on Red Hat) for which I'm using the admin application to create and modify database records. One of the fields in my underlying model is a date field. Each time the corresponding field is displayed in the admin's new or edit form I'd like the initial value of this field to be today's date (and time). The user may choose to modify it thereafter, if she desires.
I know that I can set the default field value within my model definition (i.e. in models.py). Which works fine when a database record is first created. But for subsequent invocations of the change form the callable that I've assigned to the default parameter (datetime.datetime.now) obviously doesn't get invoked.
I've looked at - and tried - pretty well all of the many proposed solutions described elsewhere in stackoverflow, without success. Most of these appear to revolve around inserting initialisation code into the ModelForm subclass, e.g. either something like this...
class ConstantDefAdminForm(ModelForm) :
a_date_field = DateField(initial="datetime.datetime.now") # or now()
class Meta :
model = ConstantDef
widgets = {
...
}
or something like this...
class ConstantDefAdminForm(ModelForm) :
class Meta :
model = ConstantDef
widgets = {
...
}
def __init__(self, ...) :
# some initialisation of a_date_field
super(ConstantDefAdminForm, self).__init__(...)
But neither of these approaches work. The initial field value is always set to the value that is stored in the database. My reading of the Django documentation is that the various ways of imposing initial field values in forms only work for unbound forms, not bound forms. Right?
But this capability (to selectively override currently stored values) would seem to be such a popular requirement that I'm convinced that there must be a way to do it.
Has anyone out there succeeded in doing this?
Thanks in advance,
Phil
In Django 1.4 the default=<callable> in model's declaration works well:
class MyModel(models.Model):
dt = models.TimeField(null=True, blank=True, default=datetime.datetime.now)
every time you add a record the default value of the field is updated.
But the use the field's default parameter cause me some problem with the Admin log history of DateField objects, that are every time recorded as changed also when they are not modified. So I've adopted a solution based on https://stackoverflow.com/a/11145346/1838607:
import datetime
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyModelAdminForm, self).__init__(*args, **kwargs)
self.fields['dt'].initial = datetime.datetime.now
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
fields = ('dt',)
Here's an approach that might work. In your model admin class, change the value of obj.a_date_field before the form is bound. The 'default' value for the date field should be the new value.
class MyModelAdmin(ModelAdmin):
...
def get_object(self, request, object_id):
obj = super(MyModelAdmin, self).get_object(request, object_id)
if obj is not None:
obj.a_date_field = datetime.now()
return obj
Note that get_object is not documented, so this is a bit hacky.
I had a similar problem, and I found the solution from here
I think what you will want to do is this:
class yourAdminModel(admin.ModelAdmin):
fields = ['your_date_field']
def add_view(self, request, form_url="", extra_context=None):
data = request.GET.copy()
data['your_date_field'] = datetime.date.today() # or whatever u need
request.GET = data
return super(yourAdminModel, self).add_view(request, form_url="", extra_context=extra_context)
You should be able to use auto_now with your DateTime Field which according to the docs will automatically set the value to now() each time the form is saved
Since Django 1.7 there is a function get_changeform_initial_data in ModelAdmin that sets initial form values:
def get_changeform_initial_data(self, request):
return {'dt': datetime.now()}

Updating Readonly Field In Admin Model

I have a deal model that contains two date field. First one is start_date and the other one is end_date. My aim is when I save a deal I want to update a readonly field which shows the substraction of these two date_field.
I try to write a widget however I can only get one of the fields. Here is my widget:
class DueToWidget(AdminDateWidget):
def render(self,name,value,attrs=None):
from datetime import timedelta
output = []
output.append(super(AdminDateWidget, self).render(name,value,attrs))
if value:
due_to = value + timedelta(days=1)
output.append(u'<p>Diff : %s</p>' % due_to)
return mark_safe(u''.join(output))
I'm adding one day to the selected date, how can I get the other field's value ? Or is there any other way to do this ?
If you don't mind having to refresh to see the diff (that is, you only see it after you save the model), then an easier approach is to add a readonly field in the admin, that points to a function, like this:
class MyModelAdmin(ModelAdmin):
readonly_fields = ('dates_difference',)
#add your other fields, or put it in a fieldset
fields = ('dates_difference',)
def dates_difference(self, model_instance):
return model_instance.end_date - model_instance.start_date
Since your goal is just to display extra information in the model's admin this is the place to put the code, not in a field's widget or the model's class.
As the readonly_fields documentation specifies, its behavior is nearly identical as the list_display, that is you can point it to attributes on both the model and the model's admin, and also to callables and methods.
Override save() in the model to save computed data.
def save( self, *args, **kw ):
self.diff = self.end_date - self.start_date
return super( YourModelClass, self ).save( *args, **kw )
Learn more by reading the Django documentation on the subject.

Django TimeField Model without seconds

Greetings,
I am trying to implement a TimeField model which only consists of HH:MM (ie 16:46) format, I know it is possible to format a regular Python time object but I am lost about how to manage this with Django.
Cheers
Django widget can be used to achieve this easily.
from django import forms
class timeSlotForm(forms.Form):
from_time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'))
DateTime fields will always store also seconds; however, you can easily tell the template to just show the hours and minute, with the time filter:
{{ value|time:"H:M" }}
where "value" is the variable containing the datetime field.
Of course, you can also resort to other tricks, like cutting out the seconds from the field while saving; it would require just a small change to the code in the view handling the form, to do something like this:
if form.is_valid():
instance = form.save(commit=False)
instance.nosecs = instance.nosecs.strptime(instance.nosecs.strftime("%H:%M"), "%H:%M")
instance.save()
(note: this is an ugly and untested code, just to give the idea!)
Finally, you should note that the admin will still display the seconds in the field.
It should not be a big concern, though, because admin should be only used by a kind of users that can be instructed not to use that part of the field.
In case you want to patch also the admin, you can still assign your own widget to the form, and thus having the admin using it. Of course, this would mean a significant additional effort.
So I think the proposed and accepted solution is not optimal because with:
datetime.widget = forms.SplitDateTimeWidget(time_format=('%H:%M'))
For a SplitDateTimeField in my case but for you only change it to TimeWidget.
Hope it helps other people too.
TimeField model
in Template
Is displayed
{{ value|time:"H:i" }}
Is not displayed
{{ value|time:"H:M" }}
Django 1.4.1
For a ModelForm, you can easily add a widget like this, to avoid the seconds being shown (just show hh:mm):
class MyCreateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('time_in', 'time_out', )
widgets = {
'time_in': forms.TimeInput(format='%H:%M'),
'time_out': forms.TimeInput(format='%H:%M'),
}
You can at least modify the output in the __str__ method on the model by using datetime.time.isoformat(timespec='minutes'), like this:
def __str__(self):
return self.value.isoformat(timespec='minutes')
Now the value is showing as HH:MM in admin pages.
On Django 1.9 the following format should work:
{{ yourData.value|time:"H:i" }}
Django has a whole set of template tags and filters.
Django 1.9 documentation on this is:
https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#time