I have a subclass of models.ForeignKey, the only purpose of which is to use a custom widget:
from django.db import models
from .models import Images
class GAEImageField(models.ForeignKey):
def __init__(self, **kwargs):
super(GAEImageField, self).__init__(Images, **kwargs)
def formfield(self, *args, **kwargs):
field = forms.ModelChoiceField(self, widget=ImageUploadWidget, *args, **kwargs)
field.queryset = Images.objects.all()
return field
The problem is, when I try to use this field, any parameters to __init__ are ignored. For instance, if I try this model:
class SomethingWithImage(models.Model):
name = models.CharField('Awesome name', max_length=64)
image = GAEImageField(verbose_name='Awesome image', blank=True)
...despite the fact I specified verbose_name, the label on a generated form will be "Image" and trying to specify empty value will raise an error even though I use blank=True
Well, the problem is with your formfield method. If you look at the implementation used in the default ForeignKey for example, you'll see it calls super. I'd recommend something like this:
def formfield(self, **kwargs):
defaults = {
'widget': ImageUploadWidget,
}
defaults.update(kwargs)
return super(GAEImageField, self).formfield(**defaults)
The problem is, form fields don't extract any information from model fields. Form fields can be used completely independently from models, they don't have to be necessarily backed by a model field. That means all settings, like whether the field is required or not, its label etc. have to be passed as parameters to their constructors. It is the responsibility of the model field to create an appropriate instance of a form field, all settings included. The default implementation of django.db.models.fields.Field.formfield takes care of that which is why you usually want to call the parent's method.
As for the blank issue, try also setting null=True, otherwise even though the form will accept a blank value, the database will reject it. Also, note that modifying the value of null after syncdb has been run requires a database migration.
Related
I have a Django ModelForm where I want one field to be treated as conditionally required. I want it to be required by default, but not required depending on the value of another field. My model looks something along the lines of:
class MyModel(models.Model):
foo = models.BooleanField()
bar = models.CharField(choices=(('A', 'A'),('B', 'B'),('C', 'C')),)
Neither have null=True or blank=True so both appear as "required" when I render my form, which is what I want.
However, I have some javascript on my form template that hides the bar field input depending on the value of the input for foo. In this case, when the form is submitted, I don't want to have any validation errors about bar being required.
I am trying to implement this logic in my ModelForm's clean method, like so:
def clean(self):
data = super(MyModelForm, self).clean()
if data.get('foo') == True and 'bar' in self.errors:
del self.errors['bar']
return data
However, this still seems to give me a "This field cannot be blank." error. I've noticed that my ModelForm also has an _errors dict, but if I try adding del self._errors['bar'] I end up with an exception.
I have found some ways to do the opposite of what I want(1, 2), which is have the field not be required by default and then check it should be required in some cases in clean, but this is not what I want.
I've also tried adding data['bar'] = " " as a workaround but then I get an error about not choosing one of the choices. Ideally, I'd rather just be able to save the empty string.
The issue here is that in addition to the form's clean method being called, the form also calls post_clean on the form's instance. Because the field on the model does not have blank=True, when the model is cleaned another error is raised and it gets passed back to the form.
To resolve, set blank=True on the model. This has the unwanted side effect of making the field appear as not required on the form. To make the field required on the form, simply set the required flag in init:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__( *args, **kwargs)
self.fields['bar'].required = True
This, in addition to the clean method in the original question, is enough to create the conditionally required field that is required by default and not-required based on the value of another field.
You can override the _clean_fields method in order to provide your conditional changes:
def _clean_fields(self):
if self.data.get('foo', None) == True:
self.fields['bar'].required = False
super(MyForm, self)._clean_fields()
I have an order model with a followed_by field:
class order(models.Model):
followed_by = models.ForeignKey(User, limit_choices_to={'groups__name': "Managers"})
I have several such models and forms for those models. By default the form displays a modelchoicefield listing users that are mangers. This is fine. But the display isn't nice: it gives the username, and I want first+last name. This would work nicely: Change Django ModelChoiceField to show users' full names rather than usernames
except that now in everyform I must declare the queryset to limit users to managers. Can I use the above method so that the custom modelchoicefield defaults to my filtered queryset. so then from a form I can just say:
followed_by = ManagerUserModelChoiceField()
Can you define the queryset on your ModelChoiceField child class?
class UserModelChoiceField(ModelChoiceField):
# Query that returns set of valid choices
queryset = User.objects.filter(group__name='Managers')
def label_from_instance(self, obj):
return obj.get_full_name()
Try passing in the queryset as an argument to the ManagerUserModelChoiceField class.
followed_by = ModelChoiceField(queryset = User.objects.filter(groups__name="Managers")
After my comment to #Enrico this thought occurred to me: I overwrote the "init" class on my custom field like so:
class UserModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
super(UserModelChoiceField, self).__init__(queryset=User.objects.filter(groups__name="Managers"), *args, **kwargs)
I've seen stuff like this done in python before but I'm new to python so I'm not sure if this is a bad thing to do or if I should make this better somehow? I'd appreciate some feedback. That being said, it seems to be working correctly.
How can I override the value that is displayed for a field in the Django admin? The field contains XML and when viewing it in the admin I want to pretty-format it for easy readability. I know how to do reformatting on read and write of the field itself, but this is not what I want to do. I want the XML stored with whitespace stripped and I only want to reformat it when it is viewed in the admin change form.
How can I control the value displayed in the textarea of the admin change form for this field?
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.initial['some_field'] = some_encoding_method(self.instance.some_field)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
...
Where, some_encoding_method would be something you've set up to determine the spacing/indentation or some other 3rd-party functionality you're borrowing on. However, if you write your own method, it would be better to put it on the model, itself, and then call it through the instance:
class MyModel(models.Model):
...
def encode_some_field(self):
# do something with self.some_field
return encoded_some_field
Then:
self.instance.encode_some_field()
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()}
I have following simplified setup:
A model based on legacy data which can't be changed. Therefore I raise a ValidationError to make the user aware that there was no change made. The form fields are readonly and I could use a simple 'pass' but I prefer to get the message that save() didn't do what it was intended to do instead of just do silently nothing.
Now I'm extending the legacy data with a 2nd model which should be editable. It is included it into the legacy model's ModelAdmin as inline. I could include the CommentModel itself as a ModelAdmin, but as the LegacyModel inherits lots of functionality from parent-classes this gets complicated and un-dry.
What I want is to perform the "save" operation only on the inline-model. I thought as all fields are readonly it should work fine. Can someone give me a hint to do this in clean way?
class Legacy(models.Model):
legacyData = models.TextField()
def clean(self):
raise ValidationError("%s model is readonly." % self._meta.verbose_name.capitalize())
class Comment(models.Model):
legacy = models.OneToOneField(Legacy)
comment = models.TextField()
class LegacyAdmin(admin.ModelAdmin):
def __init__(self, *args, **kwargs):
self.readonly_fields = self.fields
super(LegacyAdmin, self).__init__(*args, **kwargs)
model = Legacy
inlines = (CommentInline, )
Thanks a lot for your time! :)
Rather than raising an exception in clean(), you could override the legacy's save() and use http://docs.djangoproject.com/en/dev/ref/contrib/messages/ to tell your user what didn't happen.