I'm new to django and trying to do something with an issue.I have this model:
class MyModel(models.Model):
value = models.CharField(max_length=50, choises=...)
custom_value = models.CharField(max_length=50, blank=True)
And I need custom_value field to be enabled to edit only if value of value field is "CUSTOM".
I'm assuming when you say 'enabled to edit' you are referring to a form field.
I'm also assuming that the data will already be saved in the model when the form loads.
In that case, this should be done on the corresponding Form for that model. You'll need to initialize that form field to be disabled, and then enable it on the form load.
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
custom_value = self.fields.get('custom_value')
if custom_value and custom_value.widget.attrs['value'] == 'CUSTOM':
self.fields['value'].widget.attrs['disabled'] = 'false'
I know your code is just an example, but using value as class attribute name is confusing.
If you were hoping to do this dynamically on one form, as in having a user select a choice and then see another option become enabled without a browser refresh, that must be done in Javascript. This is how you would do it with just Django forms.
Related
I wanted to ask if someone know how to make a selection in a form where one of the choices is to add input
in my forms.py:
class InRes(forms.ModelForm):
class Meta:
model=Results
in my models.py:
PORT_STATUS=(
('FTP','21'),
('HTTP','443,80'),
)
class Results(models.Model):
status=models.CharField(max_length=5, choices=PORT_STATUS, default='HTTP')
i want to add and option to select one of the choices or entering my own input.
any ideas?
Django forms are very picky when it comes to what is a valid choice, which is a good thing. It prevents malicious values from being passed in via manipulated POST data.
As such, you won't be able to programmatically add a choice, for one, because your choices are a hard-coded tuple, and two, because whatever choice you add won't be part of the choices the form considers valid when clean is run, because the value wasn't there when the form was initialized.
Instead, consider making the choices a separate model, and provide a separate form field to add a record for that model, so that next time, the choice will be present.
Django forms can have a mixture of model and non-model fields, so use that to add the input field. You could also control the visibility of the extra field via JavaScript. It's probably possible to combine all of this into a multi-widget for a custom field.
# theoretical code, not tested
class PortStatus(models.Model):
status = models.CharField(max_length=100)
def __unicode__(self):
return self.status
# I prefer singular model names
class Result(models.Model):
status = models.ForeignKey(PortStatus, blank=True)
class ResultsForm(forms.ModelForm):
class Meta:
model = Result
extra_choice = forms.CharField(max_length=100, required=False)
def __init__(self, *args, **kwargs):
super(ResultsForm, self).__init__(*args, **kwargs)
self.save_new_status = False
def clean(self):
cleaned_data = self.cleaned_data()
status = cleaned_data.get('status')
extra_choice = cleaned_data.get('extra_choice')
if status and extra_choice:
raise forms.ValidationError("Can't specify both")
if not status and not extra_choice:
raise forms.ValidationError('Make a selection or add status')
if not status and extra_choice:
# make sure extra_choice isn't already in choices
if PostStatus.objects.filter(
status__iexact=extra_choice).count() > 0:
raise forms.ValidationError('Status present, etc')
else:
self.save_new_status = True
return cleaned_data
def save(self, commit=True)
instance = super(ResultsForm, self).save(commit=False)
if self.save_new_status:
new_status = PostStatus.objects.create(
status=self.cleaned_data.get('extra_choice'))
self.status = new_status
if commit:
instance.save()
return instance
Example:
class MyUser(models.Model):
blocked_users = models.ManyToManyField("self", blank=True, null=True)
user = MyUser.object.get(pk=1)
user.blocked_users.add(user)
user.blocked_users.all()[0] == user # (!!!)
Can It be prevented on model/db level? Or we need just do check somewhere in app.
Looking at the Django docs for ManyToManyField arguments, it does not seem possible.
The closest argument to what you want is the limit_choices_to However, that only limits choices on ModelForms and admin (you can still save it like you did in your example), and there is currently no easy way to use it to limit based on another value (pk) in the current model.
If you want to prevent it from happening altogether, you'll have to resort to overriding the save method on the through model--something like:
class MyUser(models.Model):
blocked_users = models.ManyToManyField(..., through="BlockedUser")
class BlockedUser(models.Model):
user = models.ForeignKey(MyUser)
blocked = models.ForeignKey(MyUser)
def save(self, *args, **kwargs):
# Only allow this relationship to be created if
if self.user != self.blocked:
super(BlockedUser, self).save(*args, **kwargs)
You could of course also do this with signals.
I have a model registered on the admin site. One of its fields is a long string expression. I'd like to add custom form fields to the add/update pages of this model in the admin. Based on the values of these fields I will build the long string expression and save it in the relevant model field.
How can I do this?
I'm building a mathematical or string expression from symbols. The user chooses symbols (these are the custom fields that are not part of the model) and when they click save then I create a string expression representation from the list of symbols and store it in the DB. I don't want the symbols to be part of the model and DB, only the final expression.
Either in your admin.py or in a separate forms.py you can add a ModelForm class and then declare your extra fields inside that as you normally would. I've also given an example of how you might use these values in form.save():
from django import forms
from yourapp.models import YourModel
class YourModelForm(forms.ModelForm):
extra_field = forms.CharField()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# ...do something with extra_field here...
return super(YourModelForm, self).save(commit=commit)
class Meta:
model = YourModel
To have the extra fields appearing in the admin just:
Edit your admin.py and set the form property to refer to the form you created above.
Include your new fields in your fields or fieldsets declaration.
Like this:
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
fieldsets = (
(None, {
'fields': ('name', 'description', 'extra_field',),
}),
)
UPDATE:
In Django 1.8 you need to add fields = '__all__' to the metaclass of YourModelForm.
It it possible to do in the admin, but there is not a very straightforward way to it. Also, I would like to advice to keep most business logic in your models, so you won't be dependent on the Django Admin.
Maybe it would be easier (and maybe even better) if you have the two seperate fields on your model. Then add a method on your model that combines them.
For example:
class MyModel(models.model):
field1 = models.CharField(max_length=10)
field2 = models.CharField(max_length=10)
def combined_fields(self):
return '{} {}'.format(self.field1, self.field2)
Then in the admin you can add the combined_fields() as a readonly field:
class MyModelAdmin(models.ModelAdmin):
list_display = ('field1', 'field2', 'combined_fields')
readonly_fields = ('combined_fields',)
def combined_fields(self, obj):
return obj.combined_fields()
If you want to store the combined_fields in the database you could also save it when you save the model:
def save(self, *args, **kwargs):
self.field3 = self.combined_fields()
super(MyModel, self).save(*args, **kwargs)
Django 2.1.1
The primary answer got me halfway to answering my question. It did not help me save the result to a field in my actual model. In my case I wanted a textfield that a user could enter data into, then when a save occurred the data would be processed and the result put into a field in the model and saved. While the original answer showed how to get the value from the extra field, it did not show how to save it back to the model at least in Django 2.1.1
This takes the value from an unbound custom field, processes, and saves it into my real description field:
class WidgetForm(forms.ModelForm):
extra_field = forms.CharField(required=False)
def processData(self, input):
# example of error handling
if False:
raise forms.ValidationError('Processing failed!')
return input + " has been processed"
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# self.description = "my result" note that this does not work
# Get the form instance so I can write to its fields
instance = super(WidgetForm, self).save(commit=commit)
# this writes the processed data to the description field
instance.description = self.processData(extra_field)
if commit:
instance.save()
return instance
class Meta:
model = Widget
fields = "__all__"
You can always create new admin template, and do what you need in your admin_view (override the admin add URL to your admin_view):
url(r'^admin/mymodel/mymodel/add/$','admin_views.add_my_special_model')
If you absolutely only want to store the combined field on the model and not the two seperate fields, you could do something like this:
Create a custom form using the form attribute on your ModelAdmin. ModelAdmin.form
Parse the custom fields in the save_formset method on your ModelAdmin. ModelAdmin.save_model(request, obj, form, change)
I never done something like this so I'm not completely sure how it will work out.
The first (highest score) solution (https://stackoverflow.com/a/23337009/10843740) was accurate, but I have more.
If you declare fields by code, that solution works perfectly, but what if you want to build those dynamically?
In this case, creating fields in the __init__ function for the ModelForm won't work. You will need to pass a custom metaclass and override the declared_fields in the __new__ function!
Here is a sample:
class YourCustomMetaClass(forms.models.ModelFormMetaclass):
"""
For dynamically creating fields in ModelForm to be shown on the admin panel,
you must override the `declared_fields` property of the metaclass.
"""
def __new__(mcs, name, bases, attrs):
new_class = super(NamedTimingMetaClass, mcs).__new__(
mcs, name, bases, attrs)
# Adding fields dynamically.
new_class.declared_fields.update(...)
return new_class
# don't forget to pass the metaclass
class YourModelForm(forms.ModelForm, metaclass=YourCustomMetaClass):
"""
`metaclass=YourCustomMetaClass` is where the magic happens!
"""
# delcare static fields here
class Meta:
model = YourModel
fields = '__all__'
This is what I did to add the custom form field "extra_field" which is not the part of the model "MyModel" as shown below:
# "admin.py"
from django.contrib import admin
from django import forms
from .models import MyModel
class MyModelForm(forms.ModelForm):
extra_field = forms.CharField()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# Do something with extra_field here
return super().save(commit=commit)
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
You might get help from my answer at :
my response previous on multicheckchoice custom field
You can also extend multiple forms having different custom fields and then assigning them to your inlines class like stackedinline or tabularinline:
form =
This way you can avoid formset complication where you need to add multiple custom fields from multiple models.
so your modeladmin looks like:
inlines = [form1inline, form2inline,...]
In my previous response to the link here, you will find init and save methods.
init will load when you view the page and save will send it to database.
in these two methods you can do your logic to add strings and then save thereafter view it back in Django admin change_form or change_list depending where you want.
list_display will show your fields on change_list.
Let me know if it helps ...
....
class CohortDetailInline3(admin.StackedInline):
model = CohortDetails
form = DisabilityTypesForm
...
class CohortDetailInline2(admin.StackedInline):
model = CohortDetails
form = StudentRPLForm
...
...
#admin.register(Cohort)
class CohortAdmin(admin.ModelAdmin):
form = CityInlineForm
inlines = [uploadInline, cohortDetailInline1,
CohortDetailInline2, CohortDetailInline3]
list_select_related = True
list_display = ['rto_student_code', 'first_name', 'family_name',]
...
I am relatively new to Django and I'm trying to achieve something that is not quite clear in the documentation. My application requires multiple types of users. So, I have extended django User, creating a Profile model that contains additional fields common in all User types:
USER_TYPES = (
('A', 'UserTypeA'),
('B', 'UserTypeB'),
('C', 'UserTypeC'),
)
class Profile(models.Model):
user = models.OneToOneField(User, unique=True)
about = models.TextField(blank=True)
user_type = models.CharField(max_length=3, choices=USER_TYPES, default='A')
def save(self, *args, **kwargs):
try:
existing = Profile.objects.get(user=self.user)
self.id = existing.id #force update instead of insert
except Profile.DoesNotExist:
print "Profile not created yet"
models.Model.save(self, *args, **kwargs)
def create_user(sender, instance, created, **kwargs):
print "User Profile Creation: False"
if created:
print "User Profile Creation: ", created
Profile.objects.create(user=instance)
post_save.connect(create_user, sender=Profile)
In settings.py I have set:
AUTH_PROFILE_MODULE = 'users.Profile'
After that i have defined my UserTypeX models deriving from Profile models like this:
class UserTypeA(Profile):
phone_number = models.CharField(max_length=13,blank=False)
class UserTypeB(Profile):
company_name = models.CharField(max_length=30,blank=False)
phone_number = models.CharField(max_length=13,blank=False)
def __init__(self, *args, **kwargs):
kwargs['user_type'] = 'B'
super(UserTypeB, self).__init__(*args, **kwargs)
...
I've registered those user models to admin so that I could manage my users independently.
The default admin behavior displays correctly all the Profile and UserTypeX fields and there is a select box (with a plus button next to it) for the User field - due to the OneToOneField relationship between Profile and User models. So whenever I want to create a new UserTypeX, I have to press the plus button and fill in a new popup window all the default User django defined fields.
What I am struggling to do now is display those User fields, not in a new popup window but inline in my UserTypeX add/edit page. I've read the documentation about StackedInlines and TabularInlines but those doesn't fit my case because I want to inline parent fields in an ancestor's add/edit page and not vice versa.
Is there any suggested solution with example code (please!) for that problem? Thank's in advance!
So, to make things short, is there a way to display User fields (instead of the select/add functionality due to OneToOneField relationship) in Profile add/edit screen in admin?
Update: A related question (unanswered though...) that briefly addresses the problem is:
Reverse Inlines in Django Admin
No, you cannot. But you can make B inline in A if you want. Or maybe you can manipulate how django display it in
def unicode(self):
models.py
class A(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class B(models.Model):
name = models.CharField(max_length=50)
a = models.ForeignKey(A)
admin.py
class B_Inline(admin.TabularInline):
model = B
class A_Admin(admin.ModelAdmin):
inlines = [
B_Inline,
]
admin.site.register(A, A_Admin)
admin.site.register(B)
Or maybe you want to use many-to-many relationship?
models.py
class C(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class D(models.Model):
name = models.CharField(max_length=50)
cs = models.ManyToManyField(C)
admin.py
class C_Inline(admin.TabularInline):
model = D.cs.through
class D_Admin(admin.ModelAdmin):
exclude = ("cs",)
inlines = [
C_Inline,
]
admin.site.register(C)
admin.site.register(D, D_Admin)
I don't see how this is going to work - you have told django to use users.Profile as your profile model but you actually want to use it's children. Plus as you say you're wanting to use the inline forms the "wrong way around". Without knowing more about what exactly you're trying to achieve I'd suggest that you could fix the inline form issue by defining a custom view to edit the user details rather than using django admin. As for the different profile types - I'd suggest just defining a profile type field in the model and then hiding/showing different fields/properties depending on its value
Is there an easy way to allow for required profile fields?
I am using userena in my current django project. I have a custom profile called UserProfile which has a start_year none-blank, non-null field as defined below.
class UserProfile(UserenaBaseProfile, PybbProfile):
user = models.OneToOneField(User, unique=True,
verbose_name=_('user'),
related_name='user_profile')
start_year = models.IntegerField(max_length=4)
I need this to be filled-in on signup. I created a SignupExtraForm as defined below, to override the default form.
class SignupFormExtra(SignupForm):
start_year = forms.IntegerField(label=_(u'Initiation Year'),
min_value=1800,
max_value=datetime.now().year,
required=True)
def save(self):
new_user = super(SignupFormExtra, self).save()
new_user_profile = new_user.get_profile()
new_user_profile.start_year = self.cleaned_data['start_year']
new_user_profile.save()
# Userena expects to get the new user from this form, so return the new
# user.
return new_user
When I attempt to add a new user thru the now modified form I get the below error:
profile_userprofile.start_year may not be NULL
With the stack trace pointing at new_user = super(SignupFormExtra, self).save()), in the code above.
I think this has to do with the user profile being created and saved before I am able to give it the required data from the form. Is there an easy way of supplying this data to the user_creation process, or delaying the creating of the user profile?
Thanks
Shon
UserProfile is created after the User is saved by a post_save signal. Even if you override it with your own signal, you won't have access to the form data from there.
The easiest solution is to just allow start_year to be NULL. It's not necessary to enforce this at the database level, and you can make the field required in all forms either way:
start_year = models.IntegerField(max_length=4, blank=False, null=True)
Your custom form already enforces that the field is required, so you're done.
UPDATE (from comment)
Custom form, yes, but you can still use a ModelForm:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['start_year'].required = True
UPDATE (again)
Actually, I didn't think before my last update. I put blank=False on the start_year field in my example. That will force that the field is required in all ModelForms by default. You don't need a custom ModelForm at all. However, I left the previous update for posterity.