django_tables2 mixins, display non-joinable models with concatenated rows - python

For some contrived reason I have two QuerySets which match up in row-order, but don't have a shared foreign key, so a join is not possible. I don't have the ability to add a key here, so I'd like to do a "hstack" of the two results and display as a table. This is easy with jinja templating, but I'd like to use the convenience functions of tables2 (e.g. sorting, etc) and I would like to still retain the ability to do foreign key traversal on each queryset.
Equivalently, consider providing a QuerySet and a list of external data that is the result of an operation on that QuerySet.
qs = ModelA.objects.filter( ... ) # queryset
ext_data = get_metadata_from_elsewhere(qs) # returns a list of dict
# len(qs) == len(ext_data)
For example, with two models I can create a Mixin:
class ModelATable(tables.Table):
class Meta:
model = ModelA
class ModelBTable(ModelATable, tables.Table):
class Meta:
model = ModelB
Which produces a rendered table with the fields from both models. If I supply ModelBTable(query_model_b) then only those fields are displayed as expected, and similarly for ModelBTable(query_model_a). How do I provide both query_model_a and query_model_b?
Also if there's an easy way to do hstack(query_a, query_b) then that seems like it'd be easier. Providing a dictionary of the combined results isn't great because I lose access to foreign keys, but I suppose I could add some logic to do that while generating the merged dictionary? But it's nice that tables2 automatically infers things based on the model field type, and I'd lose that.
I assume internally tables.Table just iterates through the provided data and tries to access by key. So I think I need to provide a data object that can resolve names from either model?
EDIT: Seems like defining the columns to be returned and providing a custom accessor might do the job, but it seems like overkill and I don't know which functions ought to be overridden (resolve, at least).

Related

Get all values from Django QuerySet plus additional fields from a related model

I was wondering if there is a shortcut to getting all fields from a Django model and only defining additional fields that are retrieved through a join (or multiple joins).
Consider models like the following:
class A(models.Model):
text = models.CharField(max_length=10, blank=True)
class B(models.Model):
a = models.ForeignKey(A, null=True, on_delete=models.CASCADE)
y = models.PositiveIntegerField(null=True)
Now I can use the values() function like this
B.objects.values('y', 'a__text')
to get tuples containing the specified values from the B model and the actual field from the A model. If I only use
B.objects.values()
I only get tuples containing fields from the B model (i.e., y and the foreign key id a). Let's assume a scenario where B and A have many fields, and I am interested in all of those belonging to B but only in a single field from A. Manually specifying all the field names in the values() call would be possible, but tedious and error-prone.
So is there a way to specify that I want all local fields, but only a (few) specific joined field(s)?
Note: I'm currently using Django 1.11, but if a solution only works with a more recent version I am interested in that too.
You can use prefetch_related for this. See docs:
You want to use performance optimization techniques like deferred
fields:
queryset = Pizza.objects.only('name')
restaurants = Restaurant.objects.prefetch_related(Prefetch('best_pizza', queryset=queryset))
In your case you can do something like this:
from django.db.models import Prefetch
queryset = A.objects.only('text')
b_list = B.objects.prefetch_related(Prefetch('a', queryset=queryset))
Maybe something like this would work in your case?
B.objects.select_related('a').defer('a__field_to_lazy_load');
This will load all fields from both models except the ones you specify in defer(), where you can use the usual Django double underscore convention to traverse the relationship.
The fields you specify in defer() won't be loaded from the db but they will be if you try to access them later on (e.g. in a template).

Can django filter queryset based on a dictionary field?

I am diving to a django rest-api framework that someone else wrote and configured, and I came across a problem I could not find any good solution for.
There is a model containing field of type "YAMLField". While trying to retrieve this field member, it is converted to OrderedDict (not quite sure how and where this conversion is happening...).
Now, I have a queryset of this model. I understand how to filter a queryset based on simple attributes, but how can I filter it based on this dictionary?
For example, each entry in this queryset (which is MyModel instance) contains:
MyModel.myDictionary == {'firstKey': 'firstVal', 'secondKey':'secondVal}
Now I want to get all the entries from this queryset where:
myDictionary = {'firstKey': 'Something'}
Meaning, my dictionary to filter by, may contain only a subset of the keys.
I could not find any solution or a straight forward way to do it, leading me to iterate the queryset and for each entry, iterate the dictionary.
This feels like too much overhead...
I think I had the same problem and someone told me an straightforward answer, which consists in adding "__{dictionary_key}" to your filtering request such as, in your case:
Model.objects.all().filter(myDictionary__firstKey="Something")
Though the asnwer is probably coming too late, I post it hoping that it can be useful for others in the future!
You need this is possible. For more information see django-rest-framework doc
class MultipleFieldLookupMixin(object):
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object

represent a django model which combines data from 2 other models

Background:
I scrape data from 2 sources for upcoming properties for sale, lets call one SaleAnnouncement, and the other SellerMaintainedData. They share many of the same field names (although some data can only be found in one and not the other). If an item is coming up for sale, there is guaranteed to be a SaleAnnouncement, but not necessarily SellerMaintainedData. In fact only about 10% of the "sellers" maintain there own site with relevant data. However those that do, always have more information and that data is more up to date than the data in the announcement. Also, the "announcement" is free form text which needs to go through several processing steps before the relevant data is extracted and as such, the model has some fields to store data in intermediate steps of processing (part of the reason I opted for 2 models as opposed to combining them into 1), while the "seller" data is scraped in a neat tabular format.
Problem:
I would ultimately like to combine them into one SaleItem and have implemented a model which is related to the previous 2 models and relies heavily on properties to prioritize which model the data comes from. Something like:
#property
def sale_datetime(self):
if self.sellermaintaineddata and self.sellermaintaineddata.sale_datetime:
return self.trusteeinfo.sale_datetime
else:
return self.latest_announcement and self.latest_announcement.sale_datetime
However I obviously won't be able to query those fields, which would be my end goal when listing upcoming sales. I have been suggested a solution which involves creating a custom manager which overrides the filter/exclude methods, which sounds promising but I would have to duplicate all the property field logic in the model manager.
Summary (for clarity)
I have:
class SourceA(Model):
sale_datetime = ...
address = ...
parcel_number = ...
# other attrs...
class SourceB(Model):
sale_datetime = ...
address = ...
# no parcel number here
# other attrs...
I want:
class Combined(Model):
sale_datetime = # from sourceB if sourceB else from sourceA
...
I want a unified model where common fields between SourceA and SourceB are prioritized so that if SourceB exists it derives the value of that field from SourceB or else it comes from SourceA. I would also like to query those fields so maybe using properties is not the best way...
Question
Is there a better way, should I consider restructuring my models (possibly combining those 2), or is the custom manager solution the way to go?
I would suggest another solution. What about using inheritance? You could create base class that would be abstract (https://docs.djangoproject.com/en/1.9/topics/db/models/#abstract-base-classes). You can put all common fields there and then create separate model for SaleAnnouncement and SellerMaintainedData. Since both of them will inherit from your base model, you'll have to define fields only specific for the certain model.

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')

How to find out whether a model's column is a foreign key?

I'm dynamically storing information in the database depending on the request:
// table, id and column are provided by the request
table_obj = getattr(models, table)
record = table_obj.objects.get(pk=id)
setattr(record, column, request.POST['value'])
The problem is that request.POST['value'] sometimes contains a foreign record's primary key (i.e. an integer) whereas Django expects the column's value to be an object of type ForeignModel:
Cannot assign "u'122'": "ModelA.b" must be a "ModelB" instance.
Now, is there an elegant way to dynamically check whether b is a column containing foreign keys and what model these keys are linked to? (So that I can load the foreign record by it's primary key and assign it to ModelA?) Or doesn't Django provide information like this to the programmer so I really have to get my hands dirty and use isinstance() on the foreign-key column?
You can use get_field_by_name on the models _meta object:
from django.db.models import ForeignKey
def get_fk_model(model, fieldname):
"""Returns None if not foreignkey, otherswise the relevant model"""
field_object, model, direct, m2m = model._meta.get_field_by_name(fieldname)
if not m2m and direct and isinstance(field_object, ForeignKey):
return field_object.rel.to
return None
Assuming you had a model class MyModel you would use this thus:
fk_model = get_fk_model(MyModel, 'fieldname')
Simple one liner to find all the relations to other models that exist in a model:
In [8]: relations = [f for f in Model._meta.get_fields() if (f.many_to_one or f.one_to_one) and f.auto_created]
Above will give a list of all the models with their relations.
Example:
In [9]: relations
Out[9]:
[<ManyToOneRel: app1.model1>,
<ManyToOneRel: app2.model1>,
<OneToOneRel: app1.model2>,
<OneToOneRel: app3.model5>,
<OneToOneRel: app5.model1>]
I encountered the same use case, and the accepted answer did not work for me directly. I am using Django 1.2 if it's relevant. Instead, I used the get_field_by_name method as follows.
def get_foreign_keys(self):
foreign_keys = []
for field in self._meta.fields:
if isinstance(self._meta.get_field_by_name(field.name)[0], models.ForeignKey):
foreign_keys.append(field.name)
if not foreign_keys:
return None
return foreign_keys
This is a method define inside a class. For my case, what I needed are the names of the ForeignKey fields. Cheers!
Explore the "ModelChoiceField" fields. Can they solve your problem putting foreign keys into forms for you; rather than doing that yourself.
http://docs.djangoproject.com/en/1.1/ref/forms/fields/#fields-which-handle-relationships
record = forms.ModelChoiceField(queryset=table_obj.objects.all())

Categories

Resources