I have the following two forms:
class ProfileUser(SimpleProfileUser):
photo = PhotoField(verbose_name=_('photo'))
class Client(ProfileUser):
can_be_seen = models.BooleanField(_('can be seen by other members'), default=True)
def __init__(self, *args, **kwargs):
Client._meta.get_field('phone').blank = True
What I basically have is two type of members.
I've made common class profile, where photo is not-null.
However, in Client class I want to accept empty values of photo.
This works perfectly in admin, but doesn't work on the regular site.
How can I fix that?
Code for form is:
class CommonClientForm(forms.ModelForm):
"""This form for gathering common features in both admin and member forms
"""
class Meta:
model = Client
fields = '__all__'
full_location = MarkerLocationField(*[Meta.model._meta.get_field(key)
for key in ['latitude', 'longitude', 'location']], label=_('Location'))
def __init__(self, *args, **kwargs):
individual_attrs = kwargs.pop('individual_attrs', None)
super(CommonClientForm, self).__init__(*args, **kwargs)
self.fields['birthday'].widget = SelectDateWidget(individual_attrs=individual_attrs)
if self.instance:
self.initial['full_location'] = [self.instance.latitude, self.instance.longitude, self.instance.location]
If I wrote Client._meta.get_field('phone').blank = True after the class definition (outside it), then it works.
I think the reason is that on client side fields are initialized before calling init, while in the admin after calling init.
Related
My app's models include the Service model along with three proxy models:
class Service(models.Model):
SERVICE_TYPE_CHOICES = [
('AR', 'Area'),
('OC', 'Occupancy'),
('CO', 'Consumption'),
]
service_type = models.CharField(max_length=2, choices=SERVICE_TYPE_CHOICES)
building = models.ForeignKey(Building, on_delete=models.CASCADE)
def __init__(self, *args, **kwargs):
super(HouseService, self).__init__(*args, **kwargs)
subclasses = {
'AR' : AreaService,
'OC' : OccupancyService,
'CO' : ConsumptionService,
}
self.__class__ = subclasses[self.service_type]
class AreaServiceManager(models.Manager):
def get_queryset(self):
return super(AreaServiceManager, self).get_queryset().filter(service_type='AR')
def create(self, **kwargs):
kwargs.update({'service_type': 'AR'})
return super(AreaServiceManager, self).create(**kwargs)
class AreaService(Service):
objects = AreaServiceManager()
def save(self, *args, **kwargs):
self.service_type = 'AR'
return super(AreaService, self).save(*args, **kwargs)
class Meta:
proxy = True
# Manager and Model class definitions omitted for the other two proxy models
This works as intended such that I can create instances for each proxy model transparently by registering them for the admin interface, and queries for e.g. AreaService.objects returns the proper subset of Service instances, and Service.objects.all() returns a list of instances not for the Service model, but for its subclasses.
But when I try to add a TabularInline to admin.py –
class ServiceInlineAdmin(admin.TabularInline):
model = Service
extra = 0
class BuildingAdmin(admin.ModelAdmin):
inlines = [ServiceInlineAdmin,]
– I get the following error message for the line self.__class__ = subclasses[self.service_type] in models.py (s. above):
__class__
<class 'services.models.Service'>
args
()
kwargs
{}
self
Error in formatting: RelatedObjectDoesNotExist: Service has no building.
subclasses
{'AR': <class 'services.models.AreaService'>,
'CO': <class 'services.models.ConsumptionService'>,
'OC': <class 'services.models.OccupancyService'>}
I retrieved all objects via the Django shell, and checked my database: All Service instances/DB entries are linked to a building. Commenting out the custom __init__ function solves the issue, and all linked services are displayed for each building (but I obviously lose the ability to work with the subclasses as required).
Any pointers to the cause of this error are greatly appreciated.
Problem solved. It turned out that the TabularInline form created 4 additional empty Service instances (regardless of extra = 0). To circumvent this issue, I modified my custom __init__ function like this:
def __init__(self, *args, **kwargs):
super(Service, self).__init__(*args, **kwargs)
subclasses = {
'AR' : AreaService,
'OC' : OccupancyService,
'CO' : ConsumptionService,
}
if self.id:
self.__class__ = subclasses[self.service_type]
I have an inventory management app that will be serving multiple locations (called contexts in my app). When a user is logged in, their current context is stored as a value in request.sessions.
I would like users to only be able to browse and retrieve records for their own location.
I've been trying to this by filtering the queryset that is called in the form definition to populate the select dropdown, i.e.
referenced_catalog = forms.ModelChoiceField(
queryset=Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=user_context_id),
I've tried implementing several different (but similar) approaches from various SO posts, that involve defining an init block to the form, such as:
class InventoryStockAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_context_id = kwargs.pop('user_context_id', None)
super(InventoryStockAddForm, self).__init__(*args, **kwargs)
name = forms.CharField(max_length=96,widget=forms.TextInput(),required=True)
referenced_catalog = forms.ModelChoiceField(
queryset = Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=self.user_context_id),
label = u"",
widget = ModelSelect2Widget(
model=Inventory_unit_catalog,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select catalog...', 'data-width': '35em'},
required=False))
class Meta():
model = Inventory_unit_stock
fields = ('name',)
(Different SO answers had one way or the other.)
Then in views.py:
user_context_id = request.session.get('user_context_id')
...
add_form = InventoryStockAddForm(user_context_id=user_context_id)
I've even tried using the SessionStore per https://djangobook.com/using-sessions-views-2/:
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
s = SessionStore()
user_context_id = s['user_context_id']
but it always fails at the moment the forms.py is updated as Django validates the code and cannot find a key value at the moment of validation.
Any advice would be appreciated, thanks!
You can't access self.user_context_id inside referenced_catalog = forms.ModelChoiceField(...) - that code runs when the module is loaded, not when the form is initialised.
Instead, you should set the queryset inside the __init__ method.
class InventoryStockAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_context_id = kwargs.pop('user_context_id', None)
super(InventoryStockAddForm, self).__init__(*args, **kwargs)
self.fields['referenced_catalog'].queryset = Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=self.user_context_id)
referenced_catalog = forms.ModelChoiceField(
queryset = Inventory_unit_catalog.objects.none(),
label = u"",
widget = ModelSelect2Widget(
model=Inventory_unit_catalog,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select catalog...', 'data-width': '35em'},
required=False))
I have a django ModelChoiceField that won't validate if I override the queryset.
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
form = PersonalNote()
form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
The form.is_valid() error is: "Select a valid choice. That choice is not one of the available choices".
If Tile.objects.none() is replaced with Tile.objects.all() it validates, but loads far too much data from the database. I've also tried:
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
def __init__(self, *args, **kwargs):
yyy = kwargs.pop('yyy', None)
super(PersonalNote, self).__init__(*args, **kwargs)
if yyy:
self.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
What might be wrong here? Note the real application also overrides the label, but that does not seem to be a factor here:
class ModelChoiceField2(forms.ModelChoiceField):
def label_from_instance(self, obj):
assert isinstance(obj,Tile)
return obj.child_title()
After 2 hours I found the solution. Because you specified a queryset of none in the class definition, when you instantiate that PersonalNote(request.POST) to be validated it is referenceing a null query set
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
To fix this, when you create your form based on a POST request be sure to overwrite your queryset AGAIN before you check is_valid()
def some_view_def(request):
form = PersonalNote(request.POST)
**form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)**
if form.is_valid():
#Do whatever it is
When you pass an empty queryset to ModelChoiceField you're saying that nothing will be valid for that field. Perhaps you could filter the queryset so there aren't too many options.
I also had this problem. The idea is to dynamically change the queryset of a ModelChoiceField based on a condition (in my case it was a filter made by another ModelChoiceField).
So, having the next model as example:
class FilterModel(models.Model):
name = models.CharField()
class FooModel(models.Model):
filter_field = models.ForeignKey(FilterModel)
name = models.CharField()
class MyModel(models.Model):
foo_field = models.ForeignKey(FooModel)
As you can see, MyModel has a foreign key with FooModel, but not with FilterModel. So, in order to filter the FooModel options, I added a new ModelChoiceField on my form:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
# your code here
self.fields['my_filter_field'] = forms.ModelChoiceField(FilterModel, initial=my_filter_field_selected)
self.fields['my_filter_field'].queryset = FilterModel.objects.all()
Then, on your Front-End you can use Ajax to load the options of foo_field, based on the selected value of my_filter_field. At this point everyting should be working. But, when the form is loaded, it will bring all the posible options from FooModel. To avoid this, you need to dynamically change the queryset of foo_field.
On my form view, I passed a new argument to MyForm:
id_filter_field = request.POST.get('my_filter_field', None)
form = MyForm(data=request.POST, id_filter_field=id_filter_field)
Now, you can use that argument on MyForm to change the queryset:
class MyForm(forms.ModelForm):
# your code here
def __init__(self, *args, **kwargs):
self.id_filter_field = kwargs.pop('id_filter_field', None)
# your code here
if self.id_filter_field:
self.fields['foo_field'].queryset = FooModel.objects.filter(filter_field_id=self.id_filter_field)
else:
self.fields['foo_field'].queryset = FooModel.objects.none()
I'm kinda new to Formsets and I'm stuck at a problem.
I use a Modelform to allow the creation of a new object.
class AddUpdateEntryForm(forms.ModelForm):
class Meta:
model = Zeit
exclude = ('mitarbeiter', 'user_updated')
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(AddUpdateEntryForm, self).__init__(*args, **kwargs)
self.fields['projekt'].queryset = Projekt.objects.filter(firma=Mitarbeiter.objects.get(user_id=self.user).firma_id)
That form gets it's arguments from the view:
form = AddUpdateEntryForm(user=entry_user, initial=initial)
Now, I want to display multiple instances of that form on a single page.
I use:
forms.py:
AddEntryFormSet = formset_factory(form=AddUpdateEntryForm)
and
views.py:
formset = AddEntryFormSet(initial=initial)
which works fine, but only when I comment out the "self.user...." and "self.fields...." lines from ModelForm Class.
I tried several ways of passing the argument from the call inside the view to the ModelForm.
Is there a proper way to do this?
Thanks in advance
Conrad
It should be possible to subclass BaseModelFormset so that the user is passed to each form when it is constructed. However, that's quite tricky.
A simpler technique is to define a function that creates a model form for a given user, and dynamically create the model form class in the view.
def create_form(user):
"""Returns a new model form which uses the correct queryset for user"""
class AddUpdateEntryForm(forms.ModelForm):
class Meta:
model = Zeit
exclude = ('mitarbeiter', 'user_updated')
def __init__(self, *args, **kwargs):
super(AddUpdateEntryForm, self).__init__(*args, **kwargs)
self.fields['projekt'].queryset = Projekt.objects.filter(firma=Mitarbeiter.objects.get(user_id=user).firma_id)
return AddUpdateEntryForm
The closure of user in the function means that you can set the queryset correctly. Note that the __init__ method takes the same arguments as its parent class, so we no longer have any problems when we use modelformset_factory in the view.
AddUpdateEntryForm = create_form(user)
AddEntryFormSet = modelformset_factory(model=Zeit, form=AddUpdateEntryForm)
I am trying to add dynamically new form fields (I used this blog post), for a form used in admin interface :
class ServiceRoleAssignmentForm(forms.ModelForm):
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
self.fields['test'] = forms.CharField(label='test')
class ServiceRoleAssignmentAdmin(admin.ModelAdmin):
form = ServiceRoleAssignmentForm
admin.site.register(ServiceRoleAssignment, ServiceRoleAssignmentAdmin)
However, no matter what I try, the field doesn't appear on my admin form ! Could it be a problem related to the way admin works ? Or to ModelForm ?
Thank for any help !
Sébastien
PS : I am using django 1.3
When rendering your form in template, fields enumerating from fieldsets variable, not from fields. Sure you can redefine fieldsets in your AdminForm, but then validations will fail as original form class doesn't have such field. One workaround I can propose is to define this field in form definition statically and then redefine that field in form's init method dynamically. Here is an example:
class ServiceRoleAssignmentForm(forms.ModelForm):
test = forms.Field()
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
# Here we will redefine our test field.
self.fields['test'] = forms.CharField(label='test2')
I actually have a the same issue which I'm working through at the moment.
While not ideal, I have found a temporary workaround that works for my use case. It might be of use to you?
In my case I have a static name for the field, so I just declared it in my ModelForm. as normal, I then override the init() as normal to override some options.
ie:
def statemachine_form(for_model=None):
"""
Factory function to create a special case form
"""
class _StateMachineBaseModelForm(forms.ModelForm):
_sm_action = forms.ChoiceField(choices=[], label="Take Action")
class Meta:
model = for_model
def __init__(self, *args, **kwargs):
super(_StateMachineBaseModelForm, self).__init__(*args, **kwargs)
actions = (('', '-----------'),)
for action in self.instance.sm_state_actions():
actions += ((action, action),)
self.fields['_sm_action'] = forms.ChoiceField(choices=actions,
label="Take Action")
if for_model: return _StateMachineBaseModelForm
class ContentItemAdmin(admin.ModelAdmin):
form = statemachine_form(for_model=ContentItem)
Now as I mentioned before, this is not entirely 'dynamic', but this will do for me for the time being.
I have the exact same problem that, if I add the field dynamically, without declaring it first, then it doesn't actually exist. I think this does in fact have something to do with the way that ModelForm creates the fields.
I'm hoping someone else can give us some more info.
Django - Overriding get_form to customize admin forms based on request
Try to add the field before calling the super.init:
def __init__(self, *args, **kwargs):
self.fields['test'] = forms.CharField(label='test')
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)