I've been working on updating the existing code base that was using Django 1.6 to Django 1.8. In the process, I've been facing a particular problem with aggregates.
In this code, PGDAggregate class has a method add_to_query which is intended to instantiate the SQL implementation of the aggregate and sets it as a class variable (aggregate) which i'd be using to call the as_sql method in Default SQL Aggregate(django/db.models.sql.aggregates.Aggregate) from another file.
My code (The first file, how I implement aggregate):
from django.db.models.aggregates import Aggregate
from django.db.models.sql.aggregates import Aggregate as SQLAggregate
class PGDAggregate(Aggregate):
"""
Modified to allow Aggregate functions outside of the Django module
"""
def add_to_query(self, query, alias, col, source, is_summary):
"""Add the aggregate to the nominated query.
This method is used to convert the generic Aggregate definition into a
backend-specific definition.
* query is the backend-specific query instance to which the aggregate
is to be added.
* col is a column reference describing the subject field
of the aggregate. It can be an alias, or a tuple describing
a table and column name.
* source is the underlying field or aggregate definition for
the column reference. If the aggregate is not an ordinal or
computed type, this reference is used to determine the coerced
output type of the aggregate.
* is_summary is a boolean that is set True if the aggregate is a
summary value rather than an annotation.
"""
klass = globals()['%sSQL' % self.name]
aggregate = klass(col, source=source, is_summary=is_summary, **self.extra)
# Validate that the backend has a fully supported, correct
# implementation of this aggregate
query.aggregates[alias] = aggregate
self.aggregate = aggregate
class BinSort(PGDAggregate):
alias = 'BinSort'
name = 'BinSort'
class BinSortSQL(SQLAggregate):
sql_function = ''
sql_template = '%(function)sFLOOR((IF(%(field)s<%(offset).16f,360,0)+%(field)s-%(offset).16f)/%(bincount).16f)-IF(%(field)s=%(max).16f,1,0)'
This is how I'm trying to use the aggregate attribute(an instance of Default SQL Aggregate) from the second file to invoke the as_sql method.
sortx = BinSort(xTextString, offset=x, bincount=xbin, max=x1)
sorty = BinSort(yTextString, offset=y, bincount=ybin, max=y1)
annotated_query.annotate(x=sortx, y=sorty)
cn = connections['default']
qn = SQLCompiler(annotated_query.query, cn, 'default').quote_name_unless_alias
sortx_sql = sortx.aggregate.as_sql(qn, cn)[0]
sorty_sql = sorty.aggregate.as_sql(qn, cn)[0]
The error that I'm getting(in l:6) in this implementation is,
exception 'BinSort' object has no attribute 'aggregate'
As steps of debugging, i've tried to check if the BinSort instance has an attribute "aggregate", using
hasattr(sortx, 'aggregate')
which has returned me False. But when I try to check by printing the aggregate attribute from inside the add_to_query method, I could very much see the attribute getting printed.
Also, I've implemented this in the way specified in Django 1.8 doc, https://github.com/django/django/blob/stable/1.8.x/django/db/models/aggregates.py#L46
Though this is not a solution to explain the unexpected behaviour, but this works. Since I wanted to use the as_sql() method of the Default SQL Aggregate class, i've initialized BinSortSQL directly instead of BinSort class and have used it's as_sql() method.
sortx = BinSortSQL(col, offset=x, bincount=xbin, max=x1)
sorty = BinSortSQL(col, offset=y, bincount=ybin, max=y1)
Related
I have a Flask-SQLAlchemy site which uses the SQLAlchemy enum type for certain columns, to restrict possible input values.
For example, I have a "payment type" enum that has a few payment type options, like so:
class PaymentType(enum.Enum):
fixed = "fixed"
variable = "fixed_delivery"
quantity_discount = "quantity_discount"
When I use this in a dropdown/select I can specify the options like so:
prd_payment_type = SelectField(_('prd_payment_type'), choices=SelectOptions.PaymentTypes.All(0, _('PleaseSelect')))
The All() function I'm calling returns the different enum values as options, and also adds a custom "Please select..." option to the dropdown list. It looks like this:
class PaymentTypes(object):
#staticmethod
def All(blank_value=None, blank_text=None):
ret = [(i.value, _(i.name)) for i in PaymentType]
SelectOption.add_blank_item(ret, blank_value, blank_text)
return ret
So far, so good, I get a nice dropdown, with the correct options. the problem arises when I want to display the form using an already existing SQLAlchemy object from the database.
When I fetch the object from SQLAlchemy, the prd_payment_type property contains the enum PaymentType.quantity_discount, not just a string value 'quantity_discount':
Because WTForms (presumably) matches the pre-selected option in the dropdown to a string value "quantity_discount", it doesn't match the enum in the SQLAlchemy model and the option is not selected in the dropdown.
The SelectField of WTForms accepts a list of tuples containing strings as choices. I can't feed it my enum.
On the other hand, the SQLAlchemy model returns an enum type as the property for prd_payment_type. I could maybe override this property or something, but that feels like a lot of work to support SQLAlchemy's enums in WTForms.
Is there a standard solution for working with SQLAlchemy's enums in a WTForms SelectField or should I abandon using enums altogether and just store strings, maybe from a list of constants to keep them in check?
I have found a solution myself. It seems that when the enum has a
def __str__(self):
return str(self.value)
That's enough for WTForms to match the database value to the SelectField value.
I have made a baseclass that derives from enum.Enum and added the code above to return the enum value as a string representation.
This solution was based on these similar problems I found later on:
https://github.com/flask-admin/flask-admin/issues/1315
Python Flask WTForm SelectField with Enum values 'Not a valid choice' upon validation
I am using mongoengine in python as an ORM. Now I have a situation where I have a class, actually the a model, of the form:
from mongoengine import *
class SomeDetails(Document):
alias = StringField(required=True)
version = StringField(required=True)
author = StringField(required=True)
someName = StringField(required=True)
Now I need to create the object of this class (and hence a document at runtime)
I can create an object of a normal class at runtime by using inspection, something like this:
from inspect import isclass
# moduleImport is a method made to facilitate importing of modules at runtime.
new_module = moduleImport('myModule')
classes = [x for x in dir(new_module) if isclass(getattr(new_module, x))]
class_object = getattr(new_module, classes[0])
instance = class_object()
And the above works perfectly. I am able to create an object at runtime and then use it as I wish.
However, in the former case above, when I need to create an object (or actually a mongo document in this case), as per the mongoengine documentation here and here, I need to create the object something like this:
docObject = SomeDetails(alias=value, version=value, author=value, someName=value)
docObject.save() # to save the document in the mongodb
wherein I need to specify the values for each of the keyword arguments while creating the object of the class, so that the document gets saved successfully.
So in this case, if I try creating the object of the SomeDetails class at runtime (as shown in the inspect example above) how can I provide these keyword arguments at the runtime while creating the object?
An important catch
So the keyword arguments are also not known before hand. They too are fetched at runtime. At runtime, before object creation, yes, I do know the list of keyword args and it's values, which is available to me as a dictionary as :
{alias:"a", version:"b", author:"c", someName:"d"}
but even then, this dic itself is available only at runtime.
You can try this:
data = {alias:"a", version:"b", author:"c", someName:"d"}
instance = class_object(**data)
More on **kwargs here
Presumably, you would just replace the line
instance = class_object()
With
instance = class_object(alias="a", version="b", author="c", someName="d")
If this doesn't work, please elaborate on what error you get.
I have a enum table ServiceType in my database which includes "Credit", "Investment" and "Insurance" and another table Service which have two columns - type_id and type. Is there a way to call method which will be generating value of type from value of type_id while initialization?
Should I use __init__ or maybe save?
type in Service is a ForeignKey to ServiceType
Thanks, sorry for my bad English.
So, I've tried something like this:
class Service(models.Model):
type_id = models.CharField(..)
type = models.ForeignKey('ServiceType',
to_field = 'type',
default = foo(type_id))
class ServiceType(models.Model):
type = models.CharField(..)
I've found the answer if anyone will be looking for it in future.
Things like that are managed by using signals. For above example I've used pre_save signal in function decorated as reciver.
In my understanding, reciver catches data before or after saving in database - way of doing it is based on type of signal. Those functions could basicly do anything, for example - change type of Service based on given type_id.
I'm trying to render a form with a combo that shows related entities. Therefore I'm using a ModelChoiceField.
This approach works well, until I needed to limit which entities to show. If I use a simple query expression it also works well, but things break if I use a raw SQL query.
So my code that works, sets the queryset to a filter expression.
class ReservationForm(forms.Form):
location_time_slot = ModelChoiceField(queryset=LocationTimeSlot.objects.all(), empty_label="Select your prefered time")
def __init__(self,*args,**kwargs):
city_id = kwargs.pop("city_id") # client is the parameter passed from views.py
super(ReservationForm, self).__init__(*args,**kwargs)
# TODO: move this to a manager
self.fields['location_time_slot'].queryset = LocationTimeSlot.objects.filter(city__id = city_id )
BUT, if I change that to a raw query I start having problems. Code that does not work:
class ReservationForm(forms.Form):
location_time_slot = ModelChoiceField(queryset=LocationTimeSlot.objects.all(), empty_label="Select your prefered time")
def __init__(self,*args,**kwargs):
city_id = kwargs.pop("city_id") # client is the parameter passed from views.py
super(ReservationForm, self).__init__(*args,**kwargs)
# TODO: move this to a manager
query = """SELECT ts.id, ts.datetime_to, ts.datetime_from, ts.available_reserves, l.name, l.'order'
FROM reservations_locationtimeslot AS ts
INNER JOIN reservations_location AS l ON l.id = ts.location_id
WHERE l.city_id = %s
AND ts.available_reserves > 0
AND ts.datetime_from > datetime() """
time_slots = LocationTimeSlot.objects.raw(query, [city_id])
self.fields['location_time_slot'].queryset = time_slots
The first error I get when trying to render the widget is: 'RawQuerySet' object has no attribute 'all'
I could solve that one thanks to one of the commets in enter link description here, by doing:
time_slots.all = time_slots.__iter__ # Dummy fix to allow default form rendering with raw SQL
But now I'm getting something similar when posting the form:
'RawQuerySet' object has no attribute 'get'
Is there a proper way to prepare a RawQuerySet to be used by ModelChoiceField?
Thanks!
Are you sure you actually need a raw query there? Just looking at that query, I can't see any reason you can't just do it with filter(location__city=city_id, available_reserves__gte=0, datetime_from__gt=datetime.datetime.now()).
Raw query sets are missing a number of methods that are defined on conventional query sets, so just dropping them in place isn't likely to work without writing your own definitions for all those methods.
I temporarily fixed the problem adding the missing methods.
The way I'm currently using the ModelChoiceField I only needed to add the all() and get() methods, but in different scenarios you might need to add some other methods as well. Also this is not a perfect solution because:
1) Defining the get method this way migth produce incorrect results. I think the get() method is used to validate that the selected option is within the options returned by all(). The way I temporarily implemented it only validates that the id exists in the table.
2) I guess the get method is less performant specified this way.
If anyone can think of a better solution, please let me know.
So my temporary solution:
class LocationTimeSlotManager(models.Manager):
def availableSlots(self, city_id):
query = """SELECT ts.id, ts.datetime_to, ts.datetime_from, ts.available_reserves, l.name, l.'order'
FROM reservations_locationtimeslot AS ts
.....
.....
MORE SQL """
time_slots = LocationTimeSlot.objects.raw(query, [city_id])
# Dummy fix to allow default form rendering with raw SQL
time_slots.all = time_slots.__iter__
time_slots.get = LocationTimeSlot.objects.get
return time_slots
How can you know if a value is the default value for a Model's property.
For example
class Alias(models.Model) :
image = models.ImageField(upload_to='alias', default='/media/alias-default.png')
a = Alias.get("123")
# this doesn't work
if a.image == a.image.default :
pass
# nor this
if a.image == Alias.image.default :
pass
I tried digging in the docs, but didn't see anything.
You can't get it from the property itself, you have to go via the model options under model._meta.
a._meta.get_field_by_name('image')[0].get_default()
The default in Django is not the same as SQL default - it's there merely for admin to auto-fill the form field on new object creation.
If you want to compare something to value defined as default you have to define it somewhere else (i.e. in settings.py). Like:
class MyModel(models.Model):
...
my_field = models.IntegerField(default=settings.INT_DEFAULT)
The default value is stored in MyModel._meta._fields()[field_creation_index].default but be aware that this is digging in internals.