Django serving stale form validation choices - python

I have a series of views that builds a set of associations and then triggers an action based on that association.
For validation, I've built a custom function in forms.py and use that to populate the initial choices/validation values.
def targets():
o = []
for r in Target.objects.all():
o.append((r.pk, r.host.statline()))
return o
class StageForm(forms.Form):
targets = forms.MultipleChoiceField(choices=targets(), widget=forms.CheckboxSelectMultiple())
In the view, I calculate a new list pertinent to the Build Object, checking to make sure all the required parameters have been defined, and submit that as the presented choices.
hl = Target.objects.filter(build=b)
cl = []
for h in hl:
if h.host.ready():
cl.append((h.pk, h.host.statline()))
form.fields['targets'].choices = cl
The problem I'm seeing is that if I add a new host to the environment, the form operates as I expect and presents the hosts ready for a build, but then the presented option fails validation.
The list I generate from the build and the list I generate for validation is coming from the same place, and the new host is clearly in the table, but it doesn't present to the validation until I restart the development server.
To add to the strangeness, I get the expected results from the manage.py shell while the form is still validating with stale data.
Is this just because I'm running on the development server, or is my methodology here just untenable and should be switched to overriding the __init__ method?

You should be using ModelMultipleChoiceField with a queryset.
class StageForm(forms.Form):
targets = forms.ModelMultipleChoiceField(queryset=Target.objects.all(), widget=forms.CheckboxSelectMultiple())
(If the Target string representation does not output the correct display value, you can subclass the field and define label_from_formset, as shown in the docs.

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

Atomically Compare-Exchange a Model Field in Django

How can I atomically compare-exchange-save a value of Django Model instance Field? (Using PostgreSQL as the DB backend).
An example use case is making sure multiple posts with similar content (e.g. submits of the same form) take effect only once, without relying on insecure and only sometimes-working client-side javascript or server-side tracking of form UUIDs, which isn't secure against malicious multiple-posts.
For example:
def compare_exchange_save(model_object, field_name, comp, exch):
# How to implement?
....
from django.views.generic.edit import FormView
from django.db import transaction
from my_app.models import LicenseCode
class LicenseCodeFormView(FormView):
def post(self, request, ...):
# Get object matching code entered in form
license_code = LicenseCode.objects.get(...)
# Safely redeem the code exactly once
# No change is made in case of error
try:
with transaction.atomic()
if compare_exchange_save(license_code, 'was_redeemed', False, True):
# Deposit a license for the user with a 3rd party service. Raises an exception if it fails.
...
else:
# License code already redeemed, don't deposit another license.
pass
except:
# Handle exception
...
What you are looking for is the update function on a QuerySet object.
Depending on the value, you can do a comparison with Case, When objects - check out the docs on conditional updates NOTE that link is for 1.10 - Case/When came in in 1.8.
You might also find utility in using F which is used to reference a value in a field.
For example:
I need to update a value in my model Model:
(Model.objects
.filter(id=my_id)
.update(field_to_be_updated=Case(
When(my_field=True, then=Value(get_new_license_string()),
default=Value(''),
output_field=models.CharField())))
If you need to use an F object, just reference it on the right hand side of the equals in the update expression.
The update doesn't necessitate the use of transaction.atomic() context manager but if you need to do any other database operations you should continue to wrap that code with transaction.atomic()
Edit:
You may also like to use the queryset select_for_update method that implements row locks when the queryset is executed docs.

Flask-Admin: route to models view with filter applied

I have a model in Flask-Admin with filter (e.g. based on Foreign Key to other model).
I want to generate links from front-end to this model view in admin with filter value applied. I noticed that it adds ?flt0_0= to the url, so that the whole address looks kinda:
http:/.../admin/model_view_<my model>/?flt0_0=<filter value>
Which is the best way to generate routes like this?
I prefer setting named_filter_urls=True on my base view to get rid of these magic numbers (though you can just set it on any specific view as well):
class MyBaseView(BaseModelView):
...
named_filter_urls = True
class MyView(MyBaseView):
...
column_filters = ['name', 'country']
This creates URLs like: http://.../admin/model/?flt_name_equals=foo&flt_country_contains=bar (*)
With this, your URLs can easily be contructed using the name of the attribute you want to filter on. As a bonus, you don't need to have a view instance available - important if you want to link to a view for a different model.
*(When selecting filters from UI, Flask-Admin will insert integers into the parameter keys. I'm not sure why it does that, but they don't appear necessary for simple filtering.)
Flask-Admin defaults to the flt0_0=<value> syntax to be "robust across translations" if your app needs to support multiple languages. If you don't need to worry about translations, setting named_filter_urls=True is the way to go.
With named_filter_urls=True Flask-Admin generates filter query parameters like:
flt0_country_contains=<value>
The remaining integer after flt (0 in this case) is a sort key used to control the order of the filters as they appear in the UI when you have multiple filters defined. This number does not matter at all if you have a single filter.
For example, in my app I have named filters turned on. If I have multiple filters without the sort key the filters are displayed in the order they appear in the query string:
?flt_balance_smaller_than=100&flt_balance_greater_than=5
Yields: Default filter ordering
With a sort key added to the flt parameters, then I can force those filters to be displayed in a different order (flt1 will come before flt2):
?flt2_balance_smaller_than=100&flt1_balance_greater_than=5
Yields: Forced filter ordering
In practice it looks like this sort key can be any single character, e.g. this works too:
?fltB_balance_smaller_than=100&fltA_balance_greater_than=5
This behavior is ultimately defined in the Flask-Admin BaseModelView._get_list_filter_args() method here:
https://github.com/flask-admin/flask-admin/blob/master/flask_admin/model/base.py#L1714-L1739
Unfortunately, there's no public API for this yet. Here's a short snippet you can use for now to generate fltX_Y query string:
class MyView(BaseModelView):
...
def get_filter_arg(self, filter_name, filter_op='equals'):
filters = self._filter_groups[filter_name].filters
position = self._filter_groups.keys().index(filter_name)
for f in filters:
if f['operation'] == filter_op:
return 'flt%d_%d' % (position, f['index'])
Then you can call this method on a your view instance:
print my_view.get_filter_arg('Name', 'contains')

Using django-filter, why do unspecified or invalid lookup types return all results?

Here's a very simple Django Rest Framework/django-filter code example:
class MyModelFilter(django_filters.FilterSet):
class Meta:
model = MyModel
fields = {'my_str_field': ['exact']}
class MyModelList(generics.ListAPIView):
queryset = MyModel.objects.all()
filter_class = MyModelFilter
def get(self, request, format=None):
items = self.filter_queryset(self.queryset) # apply filters
serializer = MyModelSerializer(items, many=True)
return Response(serializer.data)
When I make this API call, the exact lookup type works as expected, returning matched objects:
/myobjects/?my_str_field=somevalue
If I use icontains, which as you see I did not specify as one of the supported lookup types, all objects are returned, as if the filter wasn't applied:
/myobjects/?my_str_field__icontains=this_can_be_anything
Furthermore, I can even use an invalid lookup type and there will be no error, with, again, all objects returned:
/myobjects/?my_str_field__this_can_be_anything=this_can_be_anything
This can obviously be misleading because a front-end developer who doesn't have access to the back-end code can happily think everything is fine and use the returned objects. I would expect, if not an error, at least an empty result set for the latter two cases. What am I doing wrong?
UPDATE: It appears that I should be using the strictness setting like so:
from django_filters.filterset import STRICTNESS
class MyModelFilter(django_filters.FilterSet):
# throw an exception on errors instead of returning empty results
strict = STRICTNESS.RAISE_VALIDATION_ERROR
class Meta:
model = MyModel
fields = {'my_str_field': ['exact']}
Unfortunately, this still doesn't result in an error, so my original question still stands.
If the server doesn't recognize a query string parameter, the typical behavior is to ignore that parameter. There's no standard or rfc that specifies how to handle unexpected query string parameters. But very many websites and web frameworks will liberally accept requests, and not perform any validation to reject requests that contain superfluous or misspelled query parameters.
In other words, this is not specific for Django Rest Framework.
This feature makes ajax cache busting possible. jQuery will add a parameter called _ with a random value to every single ajax request to make sure that the request has a unique url and is not cached anywhere. This would not work if the server returned an error on receiving an unexpected query parameter.

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.

Categories

Resources