Simulating noSQL in Django that uses a SQL database

In one of the project we were working on a noSQL database would make sense for user data. But we were stuck with a server that had only MySQL on it.

We tried to solve that by successfully simulating a noSQL (document) database by using a SQL (RDBSM) database.

For that a proxy Django Model was used that can be used in the same way as normal Django Models (get, set, save, delete) work but with an *infinite vertical storage.

Following 2 Django Models were used for that:

class User(models.Model):
  name = models.CharField(max_length=15)
  surname = models.CharField(max_length=31)
  email = models.EmailField()

class UserMeta(models.Model):
  user = models.ForeignKey(User)
  key = models.CharField(max_length=15)
  value = models.TextField()

The task was to have a model object with following functionality:

  • accessing user attributes and meta attributes as they where normal model attributes (ex user.age)
  • setting user attributes and meta attributes as they where nowmal model attributes (ex user.age = 25)
  • autosaving attributes in the database (on set and delete)
  • deleting meta attributes as they where normal model attributes (ex del user.age)
  • deleting model object should also delete all its meta attributes  (del user)

Following class was used as a proxy for the model:

class UserExtended():
  def __init__(self, user_id=0):
    if user_id <= 0:
      self.user = User()
    else:
      try:
        self.user = User.objects.get(id=user_id)
      except User.DoesNotExist:
        self.user = User()

  def __setattr__(self, key, value):
    if key == "user":
      self.__dict__[key] = value
    elif hasattr(self.user, key):
      setattr(self.user, key, value)
      salf.user.save()
    else:
      return self.setMeta(key, value)

  def __getattr__(self, key):
    if hasattr(self.user, key):
      return getattr(self.user, key)
    else:
      return self.getMeta(key)

  def __delattr__(self, key):
    if key == "user":
      del self.__dict__[key]
    else:
      self.delMeta(key)

  def __del__(self):
    self.user.delete()
    # delete all attributes
    UserMeta.objects.filter(user=self.user).delete()

  def hasattr(self, key):
    if hasattr(self.user, key):
      return True
    else:
      try:
        getattr(self, key)
        return True
      except:
        return False

  def setMeta(self, key, value):
    try:
      user_meta = UserMeta.objects.get(user=self.user, key=key)
    except UserMeta.DoesNotExist:
      user_meta = UserMeta(user=self.user, key=key) # new meta
    except:
      raise AttributeError("unknown error")

    user_meta.value = value
    user_meta.save()

  def getMeta(self, key):
    try:
      meta = UserMeta.objects.get(user=self.user, key=key)
      return meta.value
    except UserMeta.DoesNotExist:
      raise AttributeError("no such meta")
    except UserMeta.MultipleObjectsReturned:
      raise AttributeError("multiple objects returned")
    except:
      raise AttributeError("unknown error")

    raise AttributeError()

  def delMeta(self, key):
    try:
      return UserMeta.objects.filter(user=self.user, key=key).delete()
    except:
      return False