I have the following code which is supposed to create a MyConfig object. However, it doesn't as the app_model is always returned as None.
The idea is to choose from a select few contenttypes and then add a key, and the resulting config will trigger a bunch of services. However whenever I save the form, the contenttype stored in the app_model is always None, which is clearly undesirable.
This is in Django1.8
Here is the admin:
class ContentTypeModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "{}-{}".format(obj.app_label, obj.model)
class MyConfigForm(ModelForm):
class Meta:
model = models.MyConfig
fields = '__all__'
def __init__(self, *args, **kwargs):
super(MyConfigForm, self).__init__(*args, **kwargs)
self.fields['app_model'].label = "App Name"
app_model = ContentTypeModelChoiceField(
ContentType.objects.filter(
app_label__startswith='myApp',
model="myModel",
),
empty_label="Choose a model",
)
class MyConfigAdmin(admin.ModelAdmin):
model = models.MyConfig
form = MyConfigForm
list_display = (<display fields>
)
search_fields = (<search fields>
)
excluded_fields = ('app_model')
And here is the model itself:
class MyConfig(models.Model):
app_model = models.ForeignKey(ContentType, null=True)
ref_key = models.CharField(max_length=32, null=True)
To unfortunately somewhat have my tail between my legs. The missing code was the excluded_fields which contained app_model. I thought this removed it from the displayed fields, but it actually removes it from the data you save into the model upon save.
Thanks to everyone who looked into this. Many apologies.
Related
Imagine having a simple model like the one bellow:
from utils.validators import name_validator
class Customer(models.Model):
name = models.CharField(verbose_name="Customer Name", validators=[name_validator])
email = models.EmailField(verbose_name="Customer Email")
def __str__(self):
return self.name
Now if I explicitly define a filed on my serializer, both validators and verbose_name are lost. I can use label= and validatos= when defining the field on my serializer but I don't want to repeat myself. What if I have multiple serializer pointing to the same Model?
class CustomerSerilizer(serializers.ModelSerializer):
custom_field_name = serializers.CharField(source="name")
class Meta:
model = Customer
fields = "__all__"
Is there anyway to prevent this from happening?
I'm not sure if it's the perfect way of doing this or not, but I managed to achieve my desired behavior by writing a custom ModelSerializer which sets label and validators if they are not being passed when explicitly defining a field on the serializer.
class CustomModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(CustomModelSerializer, self).__init__(*args, **kwargs)
model = self.Meta.model
model_fields = [f.name for f in model._meta.get_fields()]
for field_name, field_instance in self.fields.items():
source_field = field_instance.source
if source_field in model_fields:
model_field = model._meta.get_field(source_field)
if "label" not in field_instance._kwargs:
field_instance.label = model_field.verbose_name
if "validators" not in field_instance._kwargs:
field_instance.validators.extend(model_field.validators)
Is it possible to use filter_horizontal for a field which is a ManyToManyField with an intermediate table such as with those without intermediate tables?
e.g:
class A(models.Model):
f1 = models.ManyToManyField(B)
f2 = models.ManyToManyField(C, through='T')
class B(models.Model):
pass
class C(models.Model):
pass
class T(models.Model):
a = models.ForeignKey(A)
c = models.ForeignKey(C)
class AAdmin(admin.ModelAdmin):
filter_horizontal = ('f1', 'f2', )
When you have a many to many field with an intermediate table, it's not possible to display the regular, filter horizontal, or filter vertical widget. The reason for this is that the intermediate table may have extra fields that can not be displayed in those widgets.
It is possible to display the related model as an inline. See the docs on working with many-to-many intermediary models for more info.
The widget's name is FilteredSelectMultiple and you have to override formfield_for_manytomany method in your admin view. Here is what i have used.
I have a Company model with manytomany courses field with Member as an intermediate model.
class Company(models.Model):
courses = models.ManyToManyField('courses.Course', through='Member', related_name='companies')
class Member(models.Model):
user = models.OneToOneField(get_user_model(),
related_name="%(app_label)s_%(class)s",
on_delete=models.CASCADE,
)
organization = TenantForeignKey(
Company, related_name="organization_users", on_delete=models.CASCADE)
This is my admin view
class CompanyAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
db = kwargs.get('using')
if db_field.name == 'courses':
kwargs['widget'] = FilteredSelectMultiple(
db_field.verbose_name, is_stacked=False
)
else:
return super().formfield_for_manytomany(db_field, request, **kwargs)
if 'queryset' not in kwargs:
queryset = Course.objects.filter(is_active=True)
if queryset is not None:
kwargs['queryset'] = queryset
form_field = db_field.formfield(**kwargs)
msg = 'Hold down “Control”, or “Command” on a Mac, to select more than one.'
help_text = form_field.help_text
form_field.help_text = (
format_lazy('{} {}', help_text, msg) if help_text else msg
)
return form_field
In my django application I have a model field called 'status'. In one of the forms to get data for this field, I only want to display a subset of all choices available in the model. Is there a way to remove a choice from a form? I need the removed choice in the database and the admin interface where I can select it.
status = models.CharField(STATUS_FIELD_NAME, choices=STATUS_CHOICES,
default=STATUS_DEFAULT,
max_length=3)
You could define the subset of choices in your form:
class YourForm(forms.ModelForm):
SUBSET_CHOICES = (
(YourModel.CHOICE_ONE, _('First choice')),
(YourModel.CHOICE_TWO, _('Second choice')),
)
class Meta:
model = YourModel
fields = ['choice', ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['choice'].choices = self.SUBSET_CHOICES
What I would like to do is to display a single form that lets the user:
Enter a document title (from Document model)
Select one of their user_defined_code choices from a drop down list (populated by the UserDefinedCode model)
Type in a unique_code (stored in the Code model)
I'm not sure how to go about displaying the fields for the foreign key relationships in a form. I know in a view you can use document.code_set (for example) to access the related objects for the current document object, but I'm not sure how to apply this to a ModelForm.
My model:
class UserDefinedCode(models.Model):
name = models.CharField(max_length=8)
owner = models.ForeignKey(User)
class Code(models.Model):
user_defined_code = models.ForeignKey(UserDefinedCode)
unique_code = models.CharField(max_length=15)
class Document(models.Model):
title = models.CharField(blank=True, null=True, max_length=200)
code = models.ForeignKey(Code)
active = models.BooleanField(default=True)
My ModelForm
class DocumentForm(ModelForm):
class Meta:
model = Document
In regards to displaying a foreign key field in a form you can use the forms.ModelChoiceField and pass it a queryset.
so, forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
def __init__(self, *args, **kwargs):
user = kwargs.pop('user','')
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['user_defined_code']=forms.ModelChoiceField(queryset=UserDefinedCode.objects.filter(owner=user))
views.py:
def someview(request):
if request.method=='post':
form=DocumentForm(request.POST, user=request.user)
if form.is_valid():
selected_user_defined_code = form.cleaned_data.get('user_defined_code')
#do stuff here
else:
form=DocumentForm(user=request.user)
context = { 'form':form, }
return render_to_response('sometemplate.html', context,
context_instance=RequestContext(request))
from your question:
I know in a view you can use
document.code_set (for example) to
access the related objects for the
current document object, but I'm not
sure how to apply this to a ModelForm.
Actually, your Document objects wouldn't have a .code_set since the FK relationship is defined in your documents model. It is defining a many to one relationship to Code, which means there can be many Document objects per Code object, not the other way around. Your Code objects would have a .document_set. What you can do from the document object is access which Code it is related to using document.code.
edit: I think this will do what you are looking for. (untested)
forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
exclude = ('code',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user','')
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['user_defined_code']=forms.ModelChoiceField(queryset=UserDefinedCode.objects.filter(owner=user))
self.fields['unique_code']=forms.CharField(max_length=15)
views.py:
def someview(request):
if request.method=='post':
form=DocumentForm(request.POST, user=request.user)
if form.is_valid():
uniquecode = form.cleaned_data.get('unique_code')
user_defined_code = form.cleaned_data.get('user_defined_code')
doc_code = Code(user_defined_code=user_defined_code, code=uniquecode)
doc_code.save()
doc = form.save(commit=False)
doc.code = doc_code
doc.save()
return HttpResponse('success')
else:
form=DocumentForm(user=request.user)
context = { 'form':form, }
return render_to_response('sometemplate.html', context,
context_instance=RequestContext(request))
actually you probably want to use get_or_create when creating your Code object instead of this.
doc_code = Code(user_defined_code=user_defined_code, code=uniquecode)
I'm using Django profiles and was inspired by James Bennett to create a dynamic form (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ )
What I need is a company field that only shows up on my user profile form when the user_type is 'pro'.
Basically my model and form look like:
class UserProfile(models.Model):
user_type = models.CharField(...
company_name = models.CharField(...
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
And I add the company_name field in init like James Bennett showed:
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
self.fields['company_name'] = forms.CharField(...
The problem is that, when I try to save() an instance of UserProfileForm, the field 'company_name' is not saved...
I have gone around this by calling the field explicitly in the save() method:
def save(self, commit=True):
upf = super(UserProfileForm, self).save(commit=False)
if 'company_name' in self.fields:
upf.company_name = self.cleaned_data['company_name']
if commit:
upf.save()
return upf
But I am not happy with this solution (what if there was more fields ? what with Django's beauty ? etc.). It kept me up at night trying to make the modelform aware of the new company_name field at init .
And that's the story of how I ended up on stackoverflow posting this...
I would remove this logic from form and move it to factory. If your logic is in factory, you can have two forms:
UserProfileForm
ProUserProfileForm
ProUserProfileForm inherits from UserProfileForm and changes only "exclude" constant.
You will have then following factory:
def user_profile_form_factory(*args, instance=None, **kwargs):
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
cls = ProUserProfileForm
else:
cls = UserProfileForm
return cls(*args, instance, **kwargs)
It seems I found a solution:
def AccountFormCreator(p_fields):
class AccountForm(forms.ModelForm):
class Meta:
model = User
fields = p_fields
widgets = {
'photo': ImageWidget()
}
return AccountForm
#...
AccountForm = AccountFormCreator( ('email', 'first_name', 'last_name', 'photo', 'region') )
if request.POST.get('acforms', False):
acform = AccountForm(request.POST, request.FILES, instance=request.u)
if acform.is_valid():
u = acform.save()
u.save()
ac_saved = True
else:
acform = AccountForm(instance = request.u)
When are you expecting the user_type property to be set? This seems like something that should be handled by javascript rather than trying to do funny things with the model form.
If you want the company_name field to appear on the client after they've designated themselves as a pro, then you can 'unhide' the field using javascript.
If instead, they've already been designated a pro user, then use another form that includes the company_name field. You can sub-class the original model form in the following manner.
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
class UserProfileProForm(UserProfileForm):
class Meta:
exclude = None # or maybe tuple() you should test it
Then in your view, you can decide which form to render:
def display_profile_view(request):
if user.get_profile().user_type == 'Pro':
display_form = UserProfileProForm()
else:
display_form = UserProfileForm()
return render_to_response('profile.html', {'form':display_form}, request_context=...)
This would be the preferred way to do it in my opinion. It doesn't rely on anything fancy. There is very little code duplication. It is clear, and expected.
Edit: (The below proposed solution does NOT work)
You could try changing the exclude of the meta class, and hope that it uses the instances version of exclude when trying to determine whether to include the field or not. Given an instance of a form:
def __init__(self, *args, **kwargs):
if self.instance.user_type == 'pro':
self._meta.exclude = None
Not sure if that will work or not. I believe that the _meta field is what is used after instantiation, but I haven't verified this. If it doesn't work, try reversing the situation.
def __init__(self, *args, **kwargs):
if self.instance.user_type != 'pro':
self._meta.exclude = ('company_name',)
And remove the exclude fields altogether in the model form declaration. The reason I mention this alternative, is because it looks like the meta class (python sense of Meta Class) will exclude the field even before the __init__ function is called. But if you declare the field to be excluded afterwards, it will exist but not be rendered.. maybe. I'm not 100% with my python Meta Class knowledge. Best of luck.
What about removing exclude = ('company_name',) from Meta class? I'd think that it is the reason why save() doesn't save company_name field