Django Concatenate Two Querysets IN ORDER - python

So I have a ModelForm with a select field. In this select field there are about 100 entries. Of these entries I have a number of entries like "Organization Colorado - Denver", which I would like to have at the top of the list, such that all the entries with "Organization Colorado" are at the top of the list, and everything else is sorted in lexicographical order.
I've tried making two separate querysets (this seems like a bad idea, but manageable with only 100 or so entries). There seems to be a lot of ways of combining these two query sets, but without maintaining the order (which is the point). I've tried this:
class CreateContactForm(ModelForm):
...
def __init__(self, *args, **kwargs):
super(CreateContactForm, self).__init__(*args, **kwargs)
p = models.ConstantContactList.objects.filter(
name__startswith=settings.PREF_ORGANIZATION_PREFIX
)
np = models.ConstantContactList.objects.filter(
name__regex=r'^(?!{})'.format(settings.PREF_ORGANIZATION_PREFIX)
).order_by('-name')
self.fields['cc_lists'].queryset = list(p) + list(np)
This doesn't work, although it might, if there was some way to convert that list back into a queryset, or if there is a way to go around the queryset maybe? I'm not sure. Can anyone provide a clue as to what I should do?

I would recommend against trying to order the querysets and just handle sorting in the rendering layer (templates or forms). This way if you want to localize your code you won't have to change your queries.
Assuming you use a forms.Select widget. You may want to inherit from this widget and override the render_menu logic in order to construct it yourself and handle the ordering yourself. You'll have access to the rendered or unrendered options, so it shouldn't be an issues from that point on.

OK. I've come up with a solution that works here, and I'm providing it to anyone else who has the same need.
from django.forms.widgets import Select
import re
class CustomOrderingWidget(Select):
def __init__(self, priority_regex, sort='+', attrs=None):
super(CustomOrderingWidget, self).__init__(attrs)
self.regex = re.compile(priority_regex)
self.sort = sort
self.template_name = 'django/forms/widgets/select.html'
def render(self, name, value, attrs=None, renderer=None):
context = self.get_context(name, value, attrs)
optgroups = context.get('widget').get('optgroups')
firsts, others = [], []
for grp in optgroups:
if self.regex.search(grp[1][0].get('label')):
firsts.append(grp)
else:
others.append(grp)
if self.sort == '+':
kfn = lambda x: x[1][0].get('label')
context['widget']['optgroups'] = sorted(firsts, key=kfn) +\
sorted(others, key=kfn)
elif self.sort == '-':
kfn = lambda x: x[1][0].get('label')
context['widget']['optgroups'] =\
sorted(firsts, key=kfn, reverse=True) +\
sorted(others, key=kfn, reverse=True)
else:
context['widget']['optgroups'] = firsts + others
return self._render(self.template_name, context, renderer)
Then you can plug it into a ModelForm like this...
import settings # YOUR personal stuffz!
class CreateContactForm(ModelForm):
...
class Meta:
...
widgets = {
# Just an example, make your own regex string!
'cc_lists': CustomOrderingWidget("^{0}".format(
settings.PREF_ORGANIZATION_PREFIX
))
}

Related

Use different decimal separators for frontend validation in Django Admin interface

I looked through every single similar question on stackoverflow and tried nearly everything.
It seems easy to do: I just want to allow , as decimal separator for a FloatField in the Django admin interface. At the moment, it depends on the localization, but I always want to allow it. It would even be ok for me, if it's just a TextInput, but I need , to work. Setting DECIMAL_SEPARATOR in settings.py does not work.
My question is similar to this 6 year old, unanswered one: How to only override DECIMAL_SEPARATOR in django while keeping localization intact?
I managed to use the TextInput widget for FloatFields like this:
class ExampleAdminForm(forms.ModelForm):
class Meta:
model = Example
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key, value in self.fields.items():
if isinstance(value, FloatField):
self.fields[key].widget = TextInput()
The widget works, but an input like 1,23 leads to an error message Enter a number. I'd have to implement my own, custom FloatField that overrides the sanitizing in to_python to allow for differenct decimal separators, but then I'd still need a different widget. This seems awfully complicated, is there a better way?
If not, how can I change the frontend validation in the widget to ignore localization and use my own regex pattern?
Patch the to_python method, or use a custom form field class that overrides that method.
One-liner answer:
self.fields[key].to_python = lambda v: self.fields[key].__class__.to_python(self.fields[key], '.'.join(v.rsplit(',', 1)) if len(v.rsplit(',', 1)[-1]) < 3 else v)
As a wrapper function:
self.fields[key].to_python = allow_comma_decimal_separator(self.fields[key].to_python)
def allow_comma_decimal_separator(old_to_python):
def to_python(value):
if ',' in value:
lvalue, decimals = value.rsplit(',', 1)
if len(decimals) < 3:
value = '.'.join((lvalue, decimals))
return old_to_python(value)
return to_python
i.e. If a comma is in the value, and the substring after the rightmost comma has a length of less than 3 (so we assume it's not a thousand separator), then we replace that comma with a period by joining the substring before and after with a period.
For explicit fields using a reusable form field class
This would be considered less "hacky".
class AllowCommaDecimalSeparatorFloatField(forms.FloatField):
def to_python(self, value):
if ',' in value:
lvalue, decimals = value.rsplit(',', 1)
if len(decimals) < 3:
value = '.'.join((lvalue, decimals))
return super().to_python(value)
In your form's Meta class:
field_classes = {
'myfield': AllowCommaDecimalSeparatorFloatField,
}
For all FloatFields
To affect all FloatField instances (not instantiated yet), place this at the top of your module:
old_to_python = forms.FloatField.to_python
def to_python(self, value):
if ',' in value:
lvalue, decimals = value.rsplit(',', 1)
if len(decimals) < 3:
value = '.'.join((lvalue, decimals))
return old_to_python(self, value)
forms.FloatField.to_python = to_python

List Manipulation Based on Class Variables

Apologies if I explain something wrong or use the wrong wording, my programmer vocabulary isn't the best. If anyone understands my problem and has better ways of explaining it feel free to do so. I have a problem similar to a problem here. I want to remove items from a list that occur in another list. But one list will have strings that reference the variable "name" within class objects.
class sword:
name = 'swordName'
class bow:
name = 'bowName'
class axe:
name = 'axeName'
inventory = [sword, bow, sword, axe]
select = ['bowName', 'swordName']
I want to be able to create a list "selectedItems" with the class objects out of inventory based off of the strings in "select" that are equal to the "name" of the class objects. It also needs to work if "inventory" and "select" both have duplicates in them.
Output:
>> inventory = [bow, axe]
>> selectedItems = [bow, sword]
One other thing I would like the program to ignore if there are more "name"s in select than there are corresponding class objects in "inventory", and to ignore if a string in "select" has no corresponding class objects.
For example, if "inventory" is [sword, axe] and "select" is ['bowName', 'non-existent', 'axeName'], the result is that "inventory" is [sword] and "selectedItems" is [axe].
A simple way of explaining this is that select will take from inventory, but if select can't take from inventory nothing happens.
You may make base class with magic methods __eq__ and __hash__ which can allow you to manage comparing your objects as you want:
class BaseItem:
name = None
def __init__(self):
self.__name = self.name
def __eq__(self, other):
return self.__name == other
def __hash__(self):
return id(self.__name)
def __repr__(self):
return f"'{self.__name}'"
class Sword(BaseItem):
name = "swordName"
class Bow(BaseItem):
name = "bowName"
class Axe(BaseItem):
name = "axeName"
inventory = [Sword(), Bow()]
select = ["swordName", "bowName", "axeName", "swordName", "bowName"]
# casting lists into sets and getting difference between them
result = set(inventory) - set(select)
print(result) # output {'swordName', 'bowName'}
eq - actually is unused here but i added that you can compare your objects with strings, lists etc:
Sword() in ["swordName"] # true
Sword() in ["bowName"] # false
Sword() == "swordName" # true
Sword() == "bowName" # false
hash - need to comparing two objects, actually it use for getting difference between two sets
repr - it is not really required method, it needs just for pretty displaying of objects
selectedItems = list()
# make a new list of the names of the objects in the inventory
# inventory and inventory names have the same index for the same item
inventory_names = [x.name for x in inventory]
for s in select:
if s in inventory_names:
index = inventory_names.index(s)
inventory_names.pop(index)
selectedItems.append(inventory.pop(index))

Django multiwidget and validation issue

I have been subclassing multiwidget to create an HTML5 range slider synchronised with a NumberInput field using this code:
class SplitRangeNumberWidget(MultiWidget):
def __init__(self):
widgets = (
forms.NumberInput(attrs={'type':'range',
'onchange':'this.nextElementSibling.value=this.value',
'oninput':'this.nextElementSibling.value=this.value',
'step':'any',
'min':0,
'max':1}),
forms.NumberInput(attrs={'step':'any',
'onchange':'this.previousElementSibling.value=this.value',
'oninput':'this.previousElementSibling.value=this.value',})
)
super(SplitRangeNumberWidget, self).__init__(widgets)
def decompress(self, value):
if value:
return [value, value]
return [None, None]
When I instanciate it and use it in a Form such as:
class ParameterForm(forms.ModelForm):
class Meta:
model = Parameter
fields = ['name','value','min_value','max_value']
widgets = {'value':SplitRangeNumberWidget()}
, the widget works fine: changing the slider or the numberinput does change the other field. However, when doing a POST, the form does not validate and I get this error in form.errors (for 3 parameters):
[{'value': ['Enter a number.']}, {'value': ['Enter a number.']},
{'value': ['Enter a number.']}]
The widgets alone do work well and form is correctly bound. But not in a multiwidget. What am I doing wrong? I added
def value_from_datadict(self, data, files, name):
num = [widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
return [float(num[0]), float(num[1])]
but this still does not work.
Thanks for your help.
I found the solution: I needed to implement
def value_from_datadict(self, data, files, name):
value_list = [widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
try:
value = value_list[0]
except ValueError:
return None
else:
return value
To paraphrase the documentation: The default implementation of value_from_datadict() returns a list of values corresponding to each Widget. This is appropriate when using a MultiWidget with a MultiValueField, but since we want to use this widget with a TextField which takes a single value, we have overridden this method to combine the data of all the subwidgets into a value.

How to create dynamic methods with python?

For my project I need to dynamically create custom (Class) methods.
I found out it is not so easy in Python:
class UserFilter(django_filters.FilterSet):
'''
This filter is used in the API
'''
# legacy below, this has to be added dynamically
#is_field_type1 = MethodFilter(action='filter_field_type1')
#def filter_field_type1(self, queryset, value):
# return queryset.filter(related_field__field_type1=value)
class Meta:
model = get_user_model()
fields = []
But it is giving me errors (and headaches...). Is this even possible?
I try to make the code between #legacy dynamic
One option to do this I found was to create the class dynamically
def create_filter_dict():
new_dict = {}
for field in list_of_fields:
def func(queryset, value):
_filter = {'stableuser__'+field:value}
return queryset.filter(**_filter)
new_dict.update({'filter_'+field: func})
new_dict.update({'is_'+field: MethodFilter(action='filter_'+field)})
return new_dict
meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)
filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('UserFilter', (django_filters.FilterSet,), filter_dict)
However, this is giving me
TypeError at /api/v2/users/
func() takes 2 positional arguments but 3 were given
Does anyone know how to solve this dilemma?
Exception Value: 'UserFilter' object has no attribute 'is_bound'
You are getting this error because the class methods you are generating, are not bound to any class. To bound them to the class, you need to use setattr()
Try this on a console:
class MyClass(object):
pass
#classmethod
def unbound(cls):
print "Now I'm bound to ", cls
print unbound
setattr(MyClass, "bound", unbound)
print MyClass.bound
print MyClass.bound()
Traceback:
UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model':
get_user_model(), 'fields':[]} )})) TypeError: type() argument 3 must
be dict, not None
Now, this is failing because dict.update() doesn't return the same instance, returns None. That can be fixed easily
class_dict = create_filter_dict()
class_dict.update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}
UserFilter = type('Foo', (django_filters.FilterSet, ), class_dict))
However, just look how messy that code looks. I recommend to you to try to be
clearer with the code you write even if it requires to write a few extra lines. In the long run, the code will be easier to maintain for you and your team.
meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)
filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('Foo', (django_filters.FilterSet,), filter_dict)
This code might not be perfect but it is more readable than the original line of code you posted:
UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}))
And removes a complication on an already kinda difficult concept to grasp.
You might want to learn about metaclasses. Maybe you can overwrite the new method of a class. I can recommend you 1 or 2 posts about that.
Another option is that maybe you are not adding the filters correctly or in a way django doesn't expect? That would explain why you get no errors but none of your functions gets called.
You can use classmethod. Here is example how you can use it:
class UserFilter:
#classmethod
def filter_field(cls, queryset, value, field = None):
# do somthing
return "{0} ==> {1} {2}".format(field, queryset, value)
#classmethod
def init(cls,list_of_fields ):
for field in list_of_fields:
ff = lambda cls, queryset, value, field=field: cls.filter_field(queryset, value, field )
setattr(cls, 'filter_'+field, classmethod( ff ))
UserFilter.init( ['a','b'] )
print(UserFilter.filter_a(1,2)) # a ==> 1 2
print(UserFilter.filter_b(3,4)) # b ==> 3 4
You are asking for:
custom (Class) methods.
So we take an existing class and derive a subclass where you can add new methods or overwrite the methods of the original existing class (look into the code of the original class for the methods you need) like this:
from universe import World
class NewEarth(World.Earth):
def newDirectionUpsideDown(self,direction):
self.rotationDirection = direction
All the other Methods and features of World.Earth apply to NewEarth only you can now change the direction to make the world turn your new way.
To overwrite an existing method of a class is as as easy as this.
class NewEarth(World.Earth):
def leIitRain(self,amount): # let's assume leIitRain() is a standard-function of our world
return self.asteroidStorm(amount) #let's assume this is possible Method of World.Earth
So if someone likes a cool shower on earth he/she/it or whatever makes room for new development on the toy marble the burning way.
So have fun in your way learning python - and don't start with complicated things.
If I got you completely wrong - you might explain your problem in more detail - so more wise people than me can share their wisdom.

Does django's Form class maintain state?

I'm building my first form with django, and I'm seeing some behavior that I really did not expect at all. I defined a form class:
class AssignmentFilterForm(forms.Form):
filters = []
filter = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
self.filters.append(PatientFilter('All'))
self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))
for i, f in enumerate(self.filters):
self.fields["filter"].choices.append((i, f.name))
When I output this form to a template using:
{{ form.as_p }}
I see the correct choices. However, after refreshing the page, I see the list three times in the select box. Hitting refresh again results in the list showing 10 times in the select box!
Here is my view:
#login_required
def assign_test(request):
pg = PhysicianGroup.objects.get(pk=physician_group)
if request.method == 'POST':
form = AssignmentFilterForm(request.POST)
if form.is_valid():
yes = False
else:
form = AssignmentFilterForm()
patients = pg.allPatients().order_by('bed__room__unit', 'bed__room__order', 'bed__order' )
return render_to_response('hospitalists/assign_test.html', RequestContext(request, {'patients': patients, 'form': form,}))
What am I doing wrong?
Thanks, Pete
This is actually a feature of Python that catches a lot of people.
When you define variables on the class as you have with filters = [] the right half of the expression is evaluated when the class is initially defined. So when your code is first run it will create a new list in memory and return a reference to this list. As a result, each AssignmentFilterForm instance will have its own filters variable, but they will all point to this same list in memory. To solve this just move the initialization of self.filters into your __init__ method.
Most of the time you don't run into this issue because the types you are using aren't stored as a reference. Numbers, booleans, etc are stored as their value. Strings are stored by reference, but strings are immutable meaning a new string must be created in memory every time it is changed and a new reference returned.
Pointers don't present themselves often in scripting language, so it's often confusing at first when they do.
Here's a simple IDLE session example to show what's happening
>>> class Test():
myList = []
def __init__( self ):
self.myList.append( "a" )
>>> Test.myList
[]
>>> test1 = Test()
>>> Test.myList
['a']
>>> test1.myList
['a']
>>> test2 = Test()
>>> test2.myList
['a', 'a']
>>> test1.myList
['a', 'a']
>>> Test.myList
['a', 'a']
I picked up the book Pro Django which answers this question. It's a great book by the way, and I highly recommend it!
The solution is to make BOTH the choice field and my helper var both instance variables:
class AssignmentFilterForm(forms.Form):
def __init__(self, pg, request = None):
super(forms.Form, self).__init__(request)
self.filters = []
self.filters.append(PatientFilter('All'))
self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))
self.addPhysicians(pg)
self.fields['filter'] = forms.ChoiceField()
for i, f in enumerate(self.filters):
self.fields['filter'].choices.append((i, f.name))
Clearing out the choices works but would surely result in threading issues.
You're appending to the PER-CLASS variable self.filters. Make it into a PER-INSTANCE variable instead, by doing self.filters = [] at the start of __init__.
To clarify from some of the other answers:
The fields are, and must be, class variables. They get all sorts of things done to them by the metaclass, and this is the correct way to define them.
However, your filters variable does not need to be a class var. It can quite easily be an instance var - just remove the definition from the class and put it in __init__. Or, perhaps even better, don't make it a property at all - just a local var within __init__. Then, instead of appending to filters.choices, just reassign it.
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
filters = []
filters.append(PatientFilter('All'))
filters.append(PatientFilter('Assigned', 'service__isnull', False))
filters.append(PatientFilter('Unassigned', 'service__isnull', True))
self.fields["filter"].choices = [(i, f.name) for i, f in enumerate(filters)]
As answered above, you need to initialize filters as an instance variable:
def __init__(...):
self.filters = []
self.filters.append(...)
# ...
If you want to know more about how the Form class works, you should read this page in the Django wiki:
Model Creation and Initialization
It talks about the internals of the Model class, but you'll find the general setup of fields is somewhat similar to the Form (minus the database stuff). It's a bit dated (2006), but I think the basic principles still apply. The metaclass stuff can be a bit confusing if you're new though.

Categories

Resources