Estou trabalhando em um aplicativo multilocatário no qual alguns usuários podem definir seus próprios campos de dados (por meio do administrador) para coletar dados adicionais em formulários e reportar os dados. O último bit faz do JSONField não uma ótima opção, então, em vez disso, tenho a seguinte solução:
class CustomDataField(models.Model):
"""
Abstract specification for arbitrary data fields.
Not used for holding data itself, but metadata about the fields.
"""
site = models.ForeignKey(Site, default=settings.SITE_ID)
name = models.CharField(max_length=64)
class Meta:
abstract = True
class CustomDataValue(models.Model):
"""
Abstract specification for arbitrary data.
"""
value = models.CharField(max_length=1024)
class Meta:
abstract = True
Observe como CustomDataField possui uma ForeignKey no site - cada site terá um conjunto diferente de campos de dados personalizados, mas use o mesmo banco de dados. Em seguida, os vários campos de dados concretos podem ser definidos como:
class UserCustomDataField(CustomDataField):
pass
class UserCustomDataValue(CustomDataValue):
custom_field = models.ForeignKey(UserCustomDataField)
user = models.ForeignKey(User, related_name='custom_data')
class Meta:
unique_together=(('user','custom_field'),)
Isso leva ao seguinte uso:
custom_field = UserCustomDataField.objects.create(name='zodiac', site=my_site) #probably created in the admin
user = User.objects.create(username='foo')
user_sign = UserCustomDataValue(custom_field=custom_field, user=user, data='Libra')
user.custom_data.add(user_sign) #actually, what does this even do?
Mas isso parece muito desajeitado, principalmente com a necessidade de criar manualmente os dados relacionados e associá-los ao modelo concreto. Existe uma abordagem melhor?
Opções que foram descartadas preventivamente:
- SQL personalizado para modificar tabelas on-the-fly. Em parte porque isso não vai ser dimensionado e em parte porque é um hack demais.
- Soluções sem esquema, como NoSQL. Não tenho nada contra eles, mas eles ainda não se encaixam. Por fim, esses dados são digitados e existe a possibilidade de usar um aplicativo de relatório de terceiros.
- JSONField, conforme listado acima, pois não funcionará bem com consultas.
Respostas:
Atualmente, existem quatro abordagens disponíveis, duas delas exigindo um certo back-end de armazenamento:
Django-eav (o pacote original não é mais mantido, mas possui alguns garfos prósperos )
Esta solução é baseada no modelo de dados Entity Attribute Value , essencialmente, usa várias tabelas para armazenar atributos dinâmicos de objetos. Grandes partes dessa solução é que ela:
permite conectar / desanexar efetivamente o armazenamento de atributos dinâmicos ao modelo do Django com comandos simples como:
Integra-se perfeitamente com o administrador do Django ;
Ao mesmo tempo, é realmente poderoso.
Desvantagens:
O uso é bem direto:
Campos Hstore, JSON ou JSONB no PostgreSQL
O PostgreSQL suporta vários tipos de dados mais complexos. A maioria é suportada por pacotes de terceiros, mas nos últimos anos o Django os adotou no django.contrib.postgres.fields.
HStoreField :
O Django-hstore era originalmente um pacote de terceiros, mas o Django 1.8 adicionou o HStoreField como um built-in, junto com vários outros tipos de campos suportados pelo PostgreSQL.
Essa abordagem é boa no sentido de permitir que você tenha o melhor dos dois mundos: campos dinâmicos e banco de dados relacional. No entanto, o hstore não é ideal em termos de desempenho , especialmente se você acabar armazenando milhares de itens em um campo. Ele também suporta apenas cadeias de valores.
No shell do Django você pode usá-lo assim:
Você pode emitir consultas indexadas nos campos hstore:
JSONField :
Os campos JSON / JSONB suportam qualquer tipo de dados codificado em JSON, não apenas pares de chave / valor, mas também tendem a ser mais rápidos e (para JSONB) mais compactos que o Hstore. Vários pacotes implementam campos JSON / JSONB, incluindo django-pgfields , mas a partir do Django 1.9, o JSONField é um built-in usando JSONB para armazenamento. O JSONField é semelhante ao HStoreField e pode ter um desempenho melhor em dicionários grandes. Ele também suporta outros tipos de strings, como números inteiros, booleanos e dicionários aninhados.
Criando no shell:
As consultas indexadas são quase idênticas ao HStoreField, exceto que o aninhamento é possível. Índices complexos podem exigir criação manual (ou uma migração com script).
Django MongoDB
Ou outras adaptações NoSQL Django - com elas você pode ter modelos totalmente dinâmicos.
As bibliotecas NoSQL Django são ótimas, mas lembre-se de que elas não são 100% compatíveis com o Django, por exemplo, para migrar para o Django-nonrel a partir do Django padrão, você precisará substituir ManyToMany por ListField, entre outras coisas.
Veja este exemplo do Django MongoDB:
Você pode até criar listas incorporadas de qualquer modelo do Django:
Django-mutant: Modelos dinâmicos baseados em syncdb e South-hooks
O Django-mutant implementa campos de Chave Estrangeira e m2m totalmente dinâmicos. E é inspirado em soluções incríveis, mas um tanto hackistas, de Will Hardy e Michael Hall.
Tudo isso é baseado nos ganchos do Django Sul, que, de acordo com a palestra de Will Hardy no DjangoCon 2011 (assista!) São, no entanto, robustos e testados em produção ( código fonte relevante ).
O primeiro a implementar isso foi Michael Hall .
Sim, isso é mágico, com essas abordagens você pode obter aplicativos, modelos e campos Django totalmente dinâmicos com qualquer back-end de banco de dados relacional. Mas a que custo? A estabilidade da aplicação sofrerá com o uso pesado? Estas são as questões a serem consideradas. Você precisa garantir um bloqueio adequado para permitir pedidos simultâneos de alteração do banco de dados.
Se você estiver usando a biblioteca de Michael Halls, seu código ficará assim:
fonte
Eu tenho trabalhado para promover a idéia do django-dynamo. O projeto ainda não está documentado, mas você pode ler o código em https://github.com/charettes/django-mutant .
Na verdade, os campos FK e M2M (consulte contrib.related) também funcionam e é até possível definir o wrapper para seus próprios campos personalizados.
Também há suporte para opções de modelo, como unique_together e ordering, além de bases de modelo, para que você possa subclassar proxy de modelo, resumo ou mixins.
Na verdade, estou trabalhando em um mecanismo de bloqueio que não está na memória para garantir que as definições de modelo possam ser compartilhadas entre várias instâncias de execução do django, impedindo-as de usar definições obsoletas.
O projeto ainda é muito alfa, mas é uma tecnologia fundamental para um dos meus projetos, então terei que levá-lo à produção pronto. O grande plano é oferecer suporte ao django-nonrel também para que possamos aproveitar o driver mongodb.
fonte
Pesquisas adicionais revelam que este é um caso um tanto especial do padrão de design do Valor da Atribuição de Entidade , que foi implementado para o Django por alguns pacotes.
Primeiro, há o projeto eav-django original , que está no PyPi.
Segundo, há uma bifurcação mais recente do primeiro projeto, o django-eav, que é principalmente um refator para permitir o uso do EAV com os próprios modelos ou modelos do django em aplicativos de terceiros.
fonte