John Peters John Peters - 3 months ago 8
Python Question

Integration of 'normal' Python class into a Django models class

I had written a Python application that had a number of classes. Let's say one of them was something like this (oversimplified - but I have tried to represent the type of functionality):

class PythonXyz(object):

def __init__(self):
self.x = []
self.y = []
self.z = []

def append(self, x, y):
self.x.append(x)
self.y.append(y)

def get_x(self):
return self.x

def get_y(self):
return self.y

def get_z(self):
return self.x + self.y

def size(self):
return len(self.x)

def dump(self):
#generate some output

def do_some_complex_stuff(self, q):
#lots of calculations and manipulation of the self.x and self.y lists.

def save(self, filename):
#some code that saves data to disk

def load(self, filename):
#some code that loads data from disk


When the app worked, but lacked in useability, I decided to djangofy the app, so that I can use the browser as a poor man's GUI and use the database goodness as well. So I made a model.py like so:

class DjangoXyzModel(models.model):
x = models.FloatField()
y = models.FloatField()


And I modified the load() and save() methods in the PythonXYZ class to use the database rather than a file, and made some views to work via the browser.

Now I ended up with 3 Django apps, [edit]with different db schemas[/edit], each with its own models.py file with several model classes and in addition to that my original code in a separate folder. I feel this is all getting very messy and it ocurred to me that it would be much cleaner design to completely integrate all the PythonXyz methods into the DjangoXyzModel class, for example:

class DjangoXyzModel(models.model):
x_db = models.FloatField()
y_db = models.FloatField()

def init_lists(self):
self.x = []
self.y = []
self.z = []

def append(self, x, y):
self.x.append(x)
self.y.append(y)

def get_x(self):
return self.x

def get_y(self):
return self.y

def get_z(self):
return self.x + self.y

def size(self):
return len(self.x)

def dump(self):
#generate some output

def do_some_complex_stuff(self, q):
#lots of calculations and manipulation of the self.x and self.y lists.

def save_to_db(self):
#some code that saves x,y lists to the database

def load_from_db(self, filename):
#some code that loads x,y lists from the database


My question is: would this approach be considered 'pollution' of the Django models class, or is this ok and just a matter of personal taste, or is this maybe exactly how the models class is supposed to be used?
If this is not recommended, or frowned upon, what would be a better way to deal with the duplication of classes?

Note, the obvious(?) solution to get rid of the lists alltogether and work straight from the database is not acceptable, because I need the lists in memory for quicker access. E.g. the lists are loaded from the database into memory once and accessed hundreds or thousands of times. Each list may be > 1000 items, so bulk read/write is also be much faster than individual access when a value is needed (it is often not sequential). In addition, often data is modified several times before finally being committed to the database.

Answer

It sounds like you should not integrate your PythonXYZ class with a Django Model. Let Django manage the database (of individual x and y FloatField records). Your code handles the list logic which is really managing collections of Model instances. It doesn't make sense to have methods on the Model that are intended to operate on something outside the scope of that individual Model instance. You might want a two-part solution — a Manager with a Collection class.

Write a custom Manager for your list-level load/save logic. That's better than a purely generic class because it conceptually ties the list operations to the database records they are manipulating. The Manager will handle the initial loading of the Model instances into those x/y/z lists and saving the list back to the database. From the documentation:

Adding extra Manager methods is the preferred way to add "table-level" functionality to your models. (For "row-level" functionality -- i.e., functions that act on a single instance of a model object -- use Model methods, not custom Manager methods.)

A custom Manager method can return anything you want. It doesn't have to return a QuerySet.

Since the lists sound like entities in their own right, you might want a Collection class that provides the list with the do_some_complex_stuff(), size(), dump(), etc methods. This is the PythonXyz object you have now. You may be able to subclass list to avoid reimplementing append() and such. The Manager load() could return this Collection class while its save() turns a Collection into the Model instances for the database write.

As far as sharing the common code across your three apps.

  • You could use an abstract Model to hold the common code with concrete app Models.
  • If you need to access the commonality at the schema level, don't use an abstract parent Model. (Then you'll have a base table and individual tables holding only the app-specific columns.)
  • Proxy Models are only when you want different behavior and the schema is the same/shared.