Django ChoiceField make only one choice readonly - python

I have a dropdown multichoice field in django, and I want to make only one value from the dropdown read only so it can't be edited, and leave the rest editable
I want if possible to do it in my view's get_form
here's what I'm doing so far :
form.fields["groups"].queryset = models.Group.objects.all()
form.fields["groups"].initial = models.Group.objects.filter(is_default=True)
So basicly I want the default value to always be selected ( selected and disabled )
Thanks for guiding me through this.

You can disable the field by setting the disabled attribute [Django-doc] to True:
form.fields['groups'].queryset = models.Group.objects.all()
form.fields['groups'].initial = models.Group.objects.filter(is_default=True)
form.fields['groups'].disabled = True
If you set this while constructing the form (in the __init__ method), it will also prevent people from making "forged" POST requests with a different value. In other words: this does not only disables the field at the browsers client, but also prevents malicious requests.

Related

Django Template session not updating values

I am developing a human vs human chess app in django. The part for pawn promotion is not working. The session values are changing,but not getting updated to django template.
The promote view
def promote(request):
#Update board with promoted piece
board = request.session['board']
target = request.session['promote_target']
board[target[0]][target[1]] = request.POST['piece']
request.session['board'] = board
request.session['promotion'] = False
request.session['player'] = 1
return render(request,'chess_app/Default.htm')
The js Function to call server
function promotion(piece){
//Function to promote pawns
//Add a confirm message
$.ajax({url:"{%url 'promote'%}",data:{'piece':piece},type:'post',success:function(){location.reload()}});
}
Everything works fine, but the session is not getting updated
It would be great if you can help.
Check this question I guess it should resolve your problem.
However IMHO use session in template is not very good solution, check this alternative options:
Middleware
You can get values from session and set it in request object in middleware. This option is sensible if you planning to use this values in different views and different templates.
View context
You can put values in view context. This option will be good if you planning to use values only in one view. (And of course you can create a mixin to share this functionality between different view
Inclusion tags
If you can extract part of template that use this data, you can create custom tag for this template and get all required data from request.
Context processor
If you need to share this values between all templates you may use context processors.
I'm not sure why this is not posted in this thread after being asked like a year ago.
The session values are changing, but not getting updated to django template.
To fix this you simply tell Django that sessions have been modified and then it knows to update them in the template:
# make Django update sessions in templates
request.session.modified = True
Here are the docs:
https://docs.djangoproject.com/en/2.2/topics/http/sessions/#when-sessions-are-saved
So to put this in context:
def promote(request):
#Update board with promoted piece
board = request.session['board']
target = request.session['promote_target']
board[target[0]][target[1]] = request.POST['piece']
request.session['board'] = board
request.session['promotion'] = False
request.session['player'] = 1
# make Django update sessions in templates
request.session.modified = True
return render(request,'chess_app/Default.htm') # you are good

How to set a default value for a Django Form Field, that would be saved even in the absence of user initiated changes?

When looking for this feature, one is flooded under answers pointing toward the Form initial member.
Yet, by its design, this approach would not save anything to database if the user does not change at least one value in the form, because the has_changed method returns False when only initial values are submitted.
Then, if one were to override has_changed to always return true, the behaviour would be to try to save forms for which no value (nor initial nor user input) is found.
Is it possible to have a real default value in Django: a value that the user can change if he wants, but that would still save the form to DB when the form is only submitted with default values ?
Here is the solution I am currently using to replace the meaning of initial to achieve the behaviour described in the question. I override the had_changed method of the Form to be:
def has_changed(self):
for name, field in self.fields.items():
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if data_value and not issubclass(field.__class__, forms.models.InlineForeignKeyField):
return True
return False
The second check (issubclass(...)) is required in case the Form is used in an InlineFormset: when editing the parent model, the foreign_key field is automatically populated for all forms displayed (even the ones that have no default values), so it prevents saving the ones that are left blank (neither defaults nor user input).
Now, on a personal and probably polemic note, I must say I hope I missed something obvious. My requirements seem quite basic, yet the solution here is nothing short of a brittle hack...
I don't think there is a premade solution for you. You'll have to do one of two things:
When the form is submitted, examine the value of the field in question. If it is equal to the default value, then ignore the result of has_changed and save it. (Be aware that this could result in duplicate items being saved, depending on your schema.)
When the form is submitted, search for an existing record with those field values. If no such record exists, save it. Otherwise do nothing. (If these records contain a last-updated timestamp, you might update that value, depending on your application.)

SelectField in wtforms and added <option> via javascript

I'm currently work on some project in pyramid and have problem with wtforms SelectField.
I have a 3 SelectField fields:
car_make (e.g., "audi")
car_model (e.g., "audi 80")
car_version (e.g., "AUDI 80 B4").
The car_make choices I can load in the view. The choices for rest of SelectFields (car_model, car_version) I will load on the client side via AJAX/javascript (I can choose car_model when car_make is selected and so on).
The problem is that when I submit the form, car_model and car_version raise 'Not valid choice' because (in SelectField.pre_validation line 431) self.choices is empty.
How can I get around this problem?
What you are asking to do is have WTForms handle "cascading selects" (having the valid fields of one choice be determined by the value of another field). There really isn't a good way using the built in fields.
The SelectField in WTForms does NOT provide you with an option to say "Don't validate that the choice supplied is valid". You MUST provide choices in order for the field to validate the choice.
As shown in the docs, while you typically could fill the choices field with a static list of choices...
class PastebinEntry(Form):
language = SelectField(u'Programming Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
...however, since you are dynamically coming up with the options, you need to set the choices attribute after instantiating the form.
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
In the above sample, the choices for "group_id" is filled dynamically in what would be your Pyramid view. So, that's what you would need to do: you need to fill the choices in your view. This is how you can fix your issue with car_make (although I think in your question you said that car_make was okay).
The problem that you have, however, is that the valid choices for car_model cannot be determined, since they depend on car_make having already been parsed and validated. WTForms doesn't really handle this well (at least with SelectFields) because it assumes that all of the fields should be validated at once. In other words, in order to generate the list of valid choices for car_model, you first need to validate the value for car_make, which is not possible to easily do given how the SelectField works.
The best way I see doing this is to create a new field type that extends the SelectField type, but removes the validation:
class NonValidatingSelectField(SelectField):
def pre_validate(self, form):
pass
This new type overrides pre_validate, which typically does the check to determine if a choice is valid.
If you use this for car_model, you won't have the error anymore. However, this now means that your field isn't actually being validated! To fix this, you can add an in-line validator on your form...
class MyForm(Form):
car_make = SelectField(u'Make', choices=[...])
car_model = NonValidatingSelectField(u'Model', choices=[])
def validate_car_model(self, field):
choices = query_for_valid_models(self.car_make.data)
# check that field.data is in choices...
You might need to tweak this a bit to get it to work exactly how you want, I haven't actually tested that this works.

Django Admin: when displaying an object, display a URL that contains one of the fields

Here is an abstract base class for many of my "Treatment" models (TreatmentA, TreatmentB, etc):
class TreatmentBase(models.Model):
URL_PREFIX = '' # child classes define this string
code = shared.models.common.RandomCharField(length=6)
class Meta:
abstract = True
Each Treatment instance has a URL, that when visited by a user, takes them to a page specific to that treatment. I want to be able to create a Treatment in Django Admin, and immediately get this URL so I can send it to users. This URL can be created with the following method on TreatmentBase:
def get_url(self):
return '{}/{}/'.format(self.URL_PREFIX, self.code)
However, I am stuck with how to get this URL to display in Django Admin. I can think of the following solutions:
(1) Customize the display of the code field so that it becomes a clickable URL. Problem: I don't know how to do this.
(2) Add the get_url method to ModelAdmin.list_display. Problem: This means I would have to define a separate list_display for each of the child models of BaseTreatment, and I would have to explicitly list all the fields of the model, meaning I have to update it every time I modify a model, violating DRY.
(3) Add an extra field like this:
url = models.URLField(default = get_url)
Problem: get_url is an instance method (since it needs to refer to the self.code field), and from my reading of the docs about the default argument, it just has to be a simple callable without arguments.
Any way to do this seemingly simple task?
You could go with option 2 (adding to the admin display) but add it to the
readonly_fields which may alleviate your DRY concerns when models changes.
Option 3 (the extra field) could also work if you override the save method setting the URL property. You'd either want to set the field as readonly in the admin or only set the value in the save method if it's currently None.

Override a Django form field from a different app

Django Guardian has two forms defined in admin.py, GroupManage and UserManage: https://github.com/lukaszb/django-guardian/blob/master/guardian/admin.py#L368
I would like to add auto-completion to these two forms, and the best way I assume to make that happen is to overwrite the group and user's field widgets (my first attempt uses django autocomplete_light.) The goal is to not need to fork django guardian.
So in my app's models.py, I added the following code
GroupManage.__class__.group = forms.CharField(max_length=81,
error_messages={'does_not_exist':
"This group does not exist!"}, widget=ChoiceWidget(True))
I also tried using setattr to no avail. In the django shell it acts like this should be working, but when the admin page gets loaded the old group variable is restored, with the default CharField widget.
The fields defined for the class are stored in the dictionary base_fields.
GroupManage.base_fields['group'] = forms.CharField(max_length=81,
error_messages={'does_not_exist':
"This group does not exist!"}, widget=ChoiceWidget(True))
Sometimes, it might be easier to alter a field attribute instead of replacing the entire field:
GroupManage.base_fields['group'].help_text = "New help text"

Categories

Resources