user908085
user908085

Reputation: 83

Single Django model, multiple tables?

I know this exact same question has been previously asked, but I was hoping for a 'better' answer (that does not involve modifying class attributes at runtime). This was the question:

Single Django model, multiple tables?

I have the same problem - I am looking for a solution similar to the responder's first reply, but that actually works. If there is no better solution, can anyone comment on how reliable the responder's solution is? It seems to me that the delay between changing the database name and querying the database could end up returning results from the wrong table:

query 1: change the name

query 2: change the name again

query 1: get results (but using the incorrect name from query 2)

Edit: The model is intended for use on ~15 tables - so inheritance is impractical, since it would require a new model name every time.

Thanks

P.S. My apologies if this is not the correct way of asking for an elaboration on a question.

Upvotes: 8

Views: 6525

Answers (2)

陈 章
陈 章

Reputation: 81

A very useful snippet.

from django.db import models
shard_tables = {}
class ShardMixin():
    @classmethod
    def shard(cls, id=None):
        def get_ext(id):   # the multi tables rule
            return str(id % 100)  
        ext = get_ext(id)
        _db_table = "%s%s" % (cls._meta.db_table , ext)  # your table name
        if _db_table not in shard_tables:
            class Meta:
                db_table = _db_table
            attrs = {
                '__module__': cls.__module__,
                'Meta': Meta,
            }
            shard_tables[_db_table] = type("%s%s" % (cls.__name__, ext), (cls, ), attrs)
        return shard_tables[_db_table]

class User(models.Model, ShardMixin):
    username = models.CharField(max_length=255, verbose_name=" username")
    class Meta:
        abstract = True
        db_table = "user_"

users = User.shard(id=3).objects.values()
print(users)

Upvotes: 0

christophe31
christophe31

Reputation: 6467

for a dynamic table and database interhangeable model with a more sexy behaviour than the one in your linked question you may use simple methods or properties:

import copy

class MyModel(models.Model):
    # anything
    @property
    def table_name(self):
        return self._meta.db_table

    @table_name.setter
    def table_name(self, value):
        new_meta = copy.copy(self._meta)
        new_meta.db_table = value
        self._meta = new_meta

    @classmethod
    def set_qs_for_table(cls, qs, table):
        my_class = copy.copy(cls)
        my_options = copy.copy(my_class._meta)
        my_class._meta = my_options
        qs.model = my_class

You may try something like this...

The copy part is to avoid danger of shared options between models. It took me time to find this part of the solution. but for the rest it looks sexy and straightforward.

Of course once in python code you may use

qs = MyClass.objects.all()
MyClass.set_qs_for_table(qs, "this_table")
my_instance = qs[0]
my_instance.table_name = "that_table"
my_instance.save(using="this_db")

Upvotes: 2

Related Questions