PyQt custom MongoDB model - model-view-controller

I'm developing an app using PyQt with MongoDB as a backend. There is a table (QTableView) in my application that should be populated with data from MongoDB and I would like to use model->view architecture. Since Qt doesn't have a model for MongoDB I need to write a custom one.
This data is currently (can be changed to fit this problem) organized as a list of dictionaries, like this (actual data is more complex):
[{"name":"some name","phone":"283891273218"}, {"name":"some other name","phone":"56958656556"}]
Each dictionary represents a row, and each key of a dictionary is a column. After a few hours of searching I got almost nowhere, and the code is:
class CustomModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(CustomModel, self).__init__()
self.datatable = None
def update(self, dataIn):
print 'Updating Model'
self.datatable = dataIn
#print 'Datatable : {0}'.format(self.datatable)
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable[0])
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
#print self.datatable
else:
return QtCore.QVariant()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled
If I understand correctly, method data should fill the rows and columns with data but I don't know how to do it. This code is currently generating the correct number of rows and columns without data.
I would appreciate any help or advice.

A good thing to start with is to ensure a consistent column ordering when pulling data from the source. Since the data sources rows are dictionaries in this case, you are not guaranteed a consistent ordering when iterating (indexing is not available anyhow). To ensure consistent ordering, setup an ordered list of column indices that map to the data sources keys:
class CustomModel(QtCore.QAbstractTableModel):
columns = ['name', 'phone']
If you want to re-use this custom model for wrapping other data sets, you probably don't want to couple the column names so tightly with the class. It could easily be made more generic by using a required instance variable passed to the initializer:
class CustomModel(QtCore.QAbstractTableModel):
def __init__(self, columns, parent=None):
super(CustomModel, self).__init__(parent)
self.columns = columns
self.datatable = []
...
model = CustomModel(['name', 'phone'])
The column names can then be used to determine the columnCount which is also more robust because len(self.datatable[0]) will fail with an empty model:
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.columns)
Furthermore, you can use the ordered columns class variable for use in a default implementation of headerData():
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.columns[section].title()
Finally, use the columns list as a lookup for converting a model column index into a dictionary key for use in the source data and return the source data value from the data() method:
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = self.datatable[index.row()]
column_key = self.columns[index.column()]
return row[column_key]
else:
return None
(Note PyQt4 and PySide don't need to use QVariant AFAICT)

Related

Django Rest Framework ModelSerializer get field data depending on query param

I have a ModelSerializer with several field, many of them are StringRelatedField so the default representation of this fields is given by the __str__ method on the model, in my case this are mostly name field in each model. In other cases I need to retrieve the id instead, so how can I do this, for example depending of a query param.
Solving this way for now, please share if someone has a better solution:
class ExampleSerializer(serializers.ModelSerializer):
...
def __init__(self, *args, **kwargs):
super(ExampleSerializer, self).__init__(*args, **kwargs)
v = self.context['request'].query_params.get('v', None) # using v query param for a "variant" handling
if v == '1': # one of my variants
self.fields['examplefield'] = serializers.StringRelatedField(many=False, allow_null=True)
Note that there is no else or another if statement because I only needed two variants, one of them for retrieving the name field, the other for the id. By default DRF will use PrimaryRelatedField (which is the id) for ModelSerializer related fields see doc

How to serialize tuples in django rest framework? Or, is there any way to convert tuple into queryset?

My views.py contains a view like:
class RankViewSet(generics.mixins.CreateModelMixin,
generics.mixins.ListModelMixin,
viewsets.GenericViewSet):
def get_queryset(self):
with connection.cursor() as cursor:
query = "SELECT ... "
cursor.execute(query)
row = cursor.fetchall()
return row
Here, get_queryset(self) returns some tuples like following:
((name1, college1, semester1), (name2, college2, semester2), (name3, college3, semester3), ...)
All of the fields are from different models, so I have to fetch these data using raw sql. How can I serialize this tuple in django rest framework? Or Is there any way to convert tuple in queryset?
Only just seen this question but answer may still be useful...
Off the top of my head you have a few options:
serializers.ListField -as you basically have a list of lists, if the content is all the same type you can do child=serializers.ListField(child=serializers.CharField())
serializers.SerializerMethodField()..and call mySerialiser(many=True) with the set
name = serializers.SerializerMethodField()
def get_name(self, obj):
return obj[0]
Having said this unless you have a very specific reason to use a raw sql query, as you are using django you should take advantage of its ORM engine and then you can use rest_framework model serializer. If speed is an issue you can use .only() and .defer() with your queries.

Dealing with unique constraints that should be replaced in django_rest_framework

tl;dr: How can I ignore (turn off) a unique constraint in django_rest_framework Create calls with a ListCreateAPIView, because I'm going to deal with it manually in the perform_create method?
Im using a third party library django-push-notifications. It has a nice model for APNSDevice (apple push notification service device) that has a unique constraint on a registration_id field.
My problem is that sometimes I want to manually delete old values in the table that have the registration ID, so that I can insert a new value. I'd like to use this serializer:
class APNSDeviceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = APNSDevice
fields = ('name', 'active', 'device_id', 'registration_id')
along with this code for PUT
class MyAppleDevices(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = APNSDeviceSerializer
model = APNSDevice
def get_queryset(self):
return APNSDevice.objects.filter(user = self.request.user)
def perform_create(self, serializer):
print "Looking for old devices with registration id "+str(self.request.registration_id)
oldDevices = APNSDevice.objects.filter(registration_id = self.request.registration_id)
for oldDevice in oldDevices:
oldDevice.delete()
apnsDevice = serializer.save(user=self.request.user)
In other words, I'm trying to manually delete other entries that have the unique constraint in this particular PUT, so that I can insert the new one without violating the unique constraint. The problem is the validator runs before the perform_create method is called, and I can't figure out how to turn off the validator's unique constraint. I tried adding this to the Serializer
def get_validation_exclusions(self, instance = None):
exclusions = super(APNSDeviceSerializer, self).get_validation_exclusions(instance)
return exclusions + ['registration_id']
but it doesn't help so obviously I have no clue even though I've been pouring through the documentation and Stack Overflow posts. Any help appreciated, thanks. I suppose as a last resort I could remove the unique constraint from the model, but it is a valid constraint so I'd rather leave it in.
I found this question because I had this exact problem with that exact library. You can get around it by subclassing the serializer and manually overriding the field definition:
class APNSDeviceSerializerWithNonUniqueRegistrationId(APNSDeviceSerializer):
registration_id = serializers.CharField(min_length=64, max_length=64)
class Meta(APNSDeviceSerializer.Meta):
fields = ("name", "registration_id", "device_id", "active", "date_created")
Then, if you're using django-push-notifications, you'll also need to override the ViewSet that uses that serializer:
class APNSDeviceAuthorizedViewSetWithNonUniqueRegistrationId(AuthorizedMixin, APNSDeviceViewSet):
"""
The out of the box viewset/serializer combo require the registration ID to be unique and won't
allow setting a registration ID to a new user (which is useful if we have potentially more than
one account on a device.)
"""
serializer_class = APNSDeviceSerializerWithNonUniqueRegistrationId
def perform_create(self, serializer):
if self.request.user.is_authenticated():
try:
existing_registration = APNSDevice.objects.get(
registration_id=serializer.validated_data['registration_id'])
existing_registration.delete()
except APNSDevice.DoesNotExist:
pass
serializer.save(user=self.request.user)
return super(DeviceViewSetMixin, self).perform_create(serializer)

how to sort a multi dimensional model

I'm using PySide to write a plugin browser. The available plugins are stored in a three dimensional model like this:
pluginType/pluginCategory/pluginName
e.g.:
python/categoryA/toolA
python/categoryB/toolAA
etc.
In my custom view, I am showing all tools of a given plugin type (i.e. "python") in a list, regardless of their category:
(python)
categoryA/toolA
categoryA/toolB
categoryA/toolC
categoryB/toolAA
categoryB/toolBB
categoryB/toolCC
I am now wondering how to best sort this view, so the tools are sorted by name regardless of their parent category. The sorting method in my current proxy model yields a sorted list per category like the above one, but what I am after is this:
(python)
categoryA/toolA
categoryB/toolAA
categoryA/toolB
categoryB/toolBB
categoryA/toolC
categoryB/toolCC
Do I have to make my proxy model convert the multi-dimensional source model into a one-dimensional one in order to achieve this or is there a better way? I would love to be able to sync the custom view with a standard tree view which is why I chose the multi-dimensional model.
Thanks,
frank
edit 1:
Here is what I have as a simplified example. I'm not sure if this is the way to go about it (changing the model structure into a 1-dimensional model), and if it is, I'm not sure how to create the data in the proxy model properly so it is linked with the source model as expected.
import sys
from PySide.QtGui import *
from PySide.QtCore import *
class ToolModel(QStandardItemModel):
'''multi dimensional model'''
def __init__(self, parent=None):
super(ToolModel, self).__init__(parent)
self.setTools()
def setTools(self):
for contRow, container in enumerate(['plugins', 'python', 'misc']):
contItem = QStandardItem(container)
self.setItem(contRow, 0, contItem)
for catRow, category in enumerate(['catA', 'catB', 'catC']):
catItem = QStandardItem(category)
contItem.setChild(catRow, catItem)
for toolRow, tool in enumerate(['toolA', 'toolB', 'toolC']):
toolItem = QStandardItem(tool)
catItem.setChild(toolRow, toolItem)
class ToolProxyModel(QSortFilterProxyModel):
'''
proxy model for sorting and filtering.
need to be able to sort by toolName regardless of category,
So I might have to convert the data from sourceModel to a 1-dimensional model?!
Not sure how to do this properly.
'''
def __init__(self, parent=None):
super(ToolProxyModel, self).__init__(parent)
def setSourceModel(self, model):
index = 0
for contRow in xrange(model.rowCount()):
containerItem = model.item(contRow, 0)
for catRow in xrange(containerItem.rowCount()):
categoryItem = containerItem.child(catRow)
for itemRow in xrange(categoryItem.rowCount()):
toolItem = categoryItem.child(itemRow)
# how to create new, 1-dimensional data for self?
app = QApplication(sys.argv)
mainWindow = QWidget()
mainWindow.setLayout(QHBoxLayout())
model = ToolModel()
proxyModel = ToolProxyModel()
proxyModel.setSourceModel(model)
treeView = QTreeView()
treeView.setModel(model)
treeView.expandAll()
listView = QListView()
listView.setModel(proxyModel)
mainWindow.layout().addWidget(treeView)
mainWindow.layout().addWidget(listView)
mainWindow.show()
sys.exit(app.exec_())
edit:
Or maybe I should be asking how to best prepare a source model so that it can be used by QTreeView but also sorted in the above mentioned way for display in a list view?!
Use a QTableView and sort (using the sort proxy) by the tool_name column.

Exposing "virtual" field in a tastypie view?

I want to create a view using tastypie to expose certain objects of the same type, but with the following two three twists:
I need to get the objects using three separate queries;
I need to add a field which doesn't exist in the underlying model, and the value of that field depends on which of the queries it came from; and
The data will be per-user (so I need to hook in to one of the methods that gets a request).
I'm not clear on how to hook into the tastypie lifecycle to accomplish this. The recommended way for adding a "virtual" field is in the dehydrate method, which only knows about the bundle it's operating on.
Even worse, there's no official way to join querysets.
My problem would go away if I could get tastypie to accept something other than a queryset. In that case I could pass it a list of subclasses of my object, with the additional field added.
I'm open to any other sensible solution.
Edit: Added twist 3 - per-user data.
In the last version you should override the dehydrate method, e.g.
def dehydrate(self, bundle):
bundle.data['full_name'] = bundle.obj.get_full_name()
return bundle
Stumbled over similar problem here. In my case, items in the list could be "checked" by user.
When an item is retrieved by AJAX, its checked status is returned with the resource as a normal field.
When an item is saved to the server, "checked" field from the resource is stored in user's session.
First I thought hydrate() and dehydrate() methods to be the best match for this job, but turned out there are problems with accessing request object in these. So I went with alter_data_to_serialize() and obj_update(). I think there's no need to override obj_create(), since item can't be checked when it's first created, I think.
Here is the code, but note that it hasn't been properly tested yet.
class ItemResource(ModelResource):
def get_object_checked_status(self, obj, request):
if hasattr(request, 'session'):
session = request.session
session_data = session.get(get_item_session_key(obj), dict())
return session_data.get('checked', False)
return False
def save_object_checked_status(self, obj, data, request):
if hasattr(request, 'session'):
session_key = get_item_session_key(obj)
session_data = request.session.get(session_key, dict())
session_data['checked'] = data.pop('checked', False)
request.session[session_key] = session_data
# Overridden methods
def alter_detail_data_to_serialize(self, request, bundle):
# object > resource
bundle.data['checked'] = \
self.get_object_checked_status(bundle.obj, request)
return bundle
def alter_list_data_to_serialize(self, request, to_be_serialized):
# objects > resource
for bundle in to_be_serialized['objects']:
bundle.data['checked'] = \
self.get_object_checked_status(bundle.obj, request)
return to_be_serialized
def obj_update(self, bundle, request=None, **kwargs):
# resource > object
save_object_checked_status(bundle.obj, bundle.data, request)
return super(ItemResource, self)\
.obj_update(bundle, request, **kwargs)
def get_item_session_key(obj): return 'item-%s' % obj.id
OK, so this is my solution. Code is below.
Points to note:
The work is basically all done in obj_get_list. That's where I run my queries, having access to the request.
I can return a list from obj_get_list.
I would probably have to override all of the other obj_* methods corresponding to the other operations (like obj_get, obj_create, etc) if I wanted them to be available.
Because I don't have a queryset in Meta, I need to provide an object_class to tell tastypie's introspection what fields to offer.
To expose my "virtual" attribute (which I create in obj_get_list), I need to add a field declaration for it.
I've commented out the filters and authorisation limits because I don't need them right now. I'd need to implement them myself if I needed them.
Code:
from tastypie.resources import ModelResource
from tastypie import fields
from models import *
import logging
logger = logging.getLogger(__name__)
class CompanyResource(ModelResource):
role = fields.CharField(attribute='role')
class Meta:
allowed_methods = ['get']
resource_name = 'companies'
object_class = CompanyUK
# should probably have some sort of authentication here quite soon
#filters does nothing. If it matters, hook them up
def obj_get_list(self, request=None, **kwargs):
# filters = {}
# if hasattr(request, 'GET'):
# # Grab a mutable copy.
# filters = request.GET.copy()
# # Update with the provided kwargs.
# filters.update(kwargs)
# applicable_filters = self.build_filters(filters=filters)
try:
#base_object_list = self.get_object_list(request).filter(**applicable_filters)
def add_role(role):
def add_role_company(link):
company = link.company
company.role = role
return company
return add_role_company
director_of = map(add_role('director'), DirectorsIndividual.objects.filter(individual__user=request.user))
member_of = map(add_role('member'), MembersIndividual.objects.filter(individual__user=request.user))
manager_of = map(add_role('manager'), CompanyManager.objects.filter(user=request.user))
base_object_list = director_of + member_of + manager_of
return base_object_list #self.apply_authorization_limits(request, base_object_list)
except ValueError, e:
raise BadRequest("Invalid resource lookup data provided (mismatched type).")
You can do something like this (not tested):
def alter_list_data_to_serialize(self, request, data):
for index, row in enumerate(data['objects']):
foo = Foo.objects.filter(baz=row.data['foo']).values()
bar = Bar.objects.all().values()
data['objects'][index].data['virtual_field'] = bar
return data

Resources