Following this SO answer and using the (excellent) Peewee-ORM I'm trying to make a versioned database in which a history of a record is stored in a second _history table. So when I create a new using the create_table() method I also need to create a second table with four extra fields.
So let's say I've got the following table:
class User(db.Model):
created = DateTimeField(default=datetime.utcnow)
name = TextField()
address = TextField()
When this table is created I also want to create the following table:
class UserHistory(db.Model):
created = DateTimeField() # Note this shouldn't contain a default anymore because the value is taken from the original User table
name = TextField()
address = TextField()
# The following fields are extra
original = ForeignKeyField(User, related_name='versions')
updated = DateTimeField(default=datetime.utcnow)
revision = IntegerField()
action = TextField() # 'INSERT' or 'UPDATE' (I never delete anything)
So I tried overriding the Model class like this:
class Model(db.Model):
#classmethod
def create_table(cls, fail_silently=False):
db.Model.create_table(cls, fail_silently=fail_silently)
history_table_name = db.Model._meta.db_table + 'history'
# How to create the history table here?
As you can see I manage to create a variable with the history table name, but from there I'm kinda lost.
Does anybody know how I can create a new table which is like the original one, but just with the added 4 fields in there? All tips are welcome!
Maybe something like this:
class HistoryModel(Model):
#classmethod
def create_table(cls...):
# Call parent `create_table()`.
Model.create_table(cls, ...)
history_fields = {
'original': ForeignKeyField(cls, related_name='versions'),
'updated': DateTimeField(default=datetime.utcnow),
'revision': IntegerField(),
'action': TextField()}
model_name = cls.__name__ + 'History'
HistoryModel = type(model_name, (cls,), history_fields)
Model.create_table(HistoryModel, ...)
Also note you'll want to do the same thing for create_indexes().
I'd suggest creating a property or some other way to easily generate the HistoryModel.
Related
I am failing to find a way I can create a self-referencing table using peewee. I am trying to create an entity similar to one on this article.
I have tried this solution here and it doesn't seem to give me the results that I want.
class Customer(Model):
name = TextField()
class CustomerDepartment(Model):
refid = ForeignKeyField(Customer, related_name='customer')
id = ForeignKeyField(Customer, related_name='department')
These are documented here: http://docs.peewee-orm.com/en/latest/peewee/models.html#self-referential-foreign-keys
class Department(BaseModel):
parent = ForeignKeyField('self', null=True, backref='children')
name = CharField()
Example use:
root = Department.create(name='root')
d1 = Department.create(parent=root, name='Dept 1')
# etc.
I want to merge 2 different models with different but overlapping fields.
I try to write a function that copy the fields with its data from model A to model B.
def create_field():
old = DetailItem.objects.all()
new = CrawlItem.objects.all()
for item in old:
c = CrawlItem.objects.get(title=item.title, description=item.description, link=item.link, cpvcode=item.cpvcode, postalcode=item.postalcode )
c.save()
and i don't know whereis the mistake. I want to have a model that contains the data from the old model and some new fields.
Here is my code for the two models:
class DetailItem(Base):
title = models.CharField(max_length=500)
description = models.CharField(max_length=20000)
link = models.URLField()
cpvcode = models.ManyToManyField('CPVCode',related_name='cpv')
postalcode = models.ForeignKey('PostalCode',on_delete=models.SET_NULL,null=True,blank=True,related_name='postal')
def __str__(self):
return self.title
class CrawlItem(Base):
guid = models.CharField( primary_key=True, max_length=500)
title = models.CharField(max_length=500)
link = models.URLField()
description = models.CharField(max_length=2000)
pubdate = models.DateTimeField()
detail = models.ForeignKey('DetailItem',on_delete=models.SET_NULL,null=True,blank=True,related_name='crawldetail')
def __str__(self):
return str(self.title)
This is what I want to get:
class CrawlItem(Base):
guid = ...
title = ...
link = ...
cpvcodes = ...
postalcode = ...
pubdate = ...
vergabestelle = ...
vergabeart = ...
anmeldefrist = ...
description = ...
Any ideas how to get there are highly appreciated!
It's not entirely clear which objects already exist in your database, and when you consider two objects to be "equal". Assuming a CrawlItem is "equal" to a "DetailItem" when title, description and link are the same, then you can use the update_or_create function like this:
for item in old:
CrawlItem.objects.update_or_create(
# if matching, existing item updated, otherwise new item created
title=item.title, description=item.description, link=item.link,
defaults = {'cpvcode': item.cpvcode, 'postalcode': item.postalcode}
)
Alternatively, if the two models are linked with the fk as shown in your models (and you want to remove it later on), then you don't even need to check for "equal" objects because you already have all the related ones (assuming title, description and link are already equal):
for item in old:
item.crawldetail.all().update(cpvcode=item.cpvcode, postalcode=item.postalcode)
in your for statement you are just trying to select de CrawlItem with the same values as DetailItem using the get queryset method.
if you want to create a CrawlItem you should use the create method (docs here -> https://docs.djangoproject.com/en/2.2/ref/models/querysets/#create)
c = CrawlItem.objects.create(title=item.title, ..., postalcode=item.postalcode)
it will be created when create() is called, so, you don't need to save it afterwards, c is set to the newly created object.
For performance reasons you can use bulk_create() method as follows (docs here -> https://docs.djangoproject.com/en/2.2/ref/models/querysets/#bulk-create)
new_crawlitems = []
for item in old:
new_crawlitems.append(CrawlItem(title=item.title, description=item.description, link=item.link, cpvcode=item.cpvcode, postalcode=item.postalcode)
CrawlItem.objects.bulk_create(new_crawlitems)
Hope this helps and put you on the right direction.
G.
I have different model. Choices of Multiselctfield of one model is dependent on another model.So , database has to be queried inside model.py While doing so, this causes problem in migration. (Table doesn't exist error)
class Invigilator(models.Model):
---
# this method queries Shift objects and Examroom
def get_invigilator_assignment_list ():
assignment = []
shifts = Shift.objects.all()
for shift in shifts:
rooms= ExamRoom.objects.all()
for room in rooms:
assign = str (shift.shiftName)+ " " +str (room.name)
assignment.append (assign)
return assignment
assignment_choice = []
assign = get_invigilator_assignment_list()
i = 0
for assignm in assign:
datatuple = (i,assignm)
assignment_choice.append(datatuple)
i= i+1
ASSIGNMENT_CHOICE = tuple(assignment_choice)
assignment =MultiSelectField (choices = ASSIGNMENT_CHOICE, blank = True, verbose_name="Assignments")
You cannot add dynamic choices because they are all stored in the migration files and table info. If Django lets you do that, this means that everytime someone adds a record to those 2 models, a new migration should be created and the db should be changed. You must approach this problem differently.
As far as I know django-smart-selects has a ChainedManyToMany field which can do the trick.
Here is an example from the repo.
from smart_selects.db_fields import ChainedManyToManyField
class Publication(models.Model):
name = models.CharField(max_length=255)
class Writer(models.Model):
name = models.CharField(max_length=255)
publications = models.ManyToManyField('Publication', blank=True, null=True)
class Book(models.Model):
publication = models.ForeignKey(Publication)
writer = ChainedManyToManyField(
Writer,
chained_field="publication",
chained_model_field="publications")
name = models.CharField(max_length=255)
This cannot be done in the model and doesn't make sense. It's like you're trying to create a column in a table with a certain fixed set of choices (what is MultiSelecField anyway?), but when someone later adds a new row in the Shift or ExamRoom table, the initial column choices have to change again.
You can
either make your assignment column a simple CharField and create the choices dynamically when creating the form
or you can try to model your relationships differently. For example, since it looks like assignment is a combination of Shift and ExamRoom, I would create a through relationship:
shifts = models.ManyToManyField(Shift, through=Assignment)
class Assignment(Model):
room = ForeignKey(ExamRoom)
shift = ForeignKey(Shift)
invigilator = ForeignKey(Invigilator)
When creating the relationship, you'd have to pick a Shift and a Room which would create the Assignment object. Then you can query things like invigilator.shifts.all() or invigilator.assignment_set.first().room.
I've had some data being gathered in production for a couple of days with, lets say, the following model:
class Tags(ndb.Model):
dt_added = ndb.DateTimeProperty(auto_now_add=True)
s_name = ndb.StringProperty(required=True, indexed=True)
Imagine I now add a new property to the model:
class Foo(ndb.Model):
is_valid = ndb.BooleanProperty(default=False)
some_key = ndb.KeyProperty(repeated=True)
class Tags(ndb.Model):
dt_added = ndb.DateTimeProperty(auto_now_add=True)
name = ndb.StringProperty(required=True, indexed=True)
new_prop = ndb.StructuredProperty(Foo)
... and gather some more data with this new model.
So now I have a portion of data that has the property new_prop set, and another portion that does not have it set.
My question is: how to I query for the data with the new property new_prop NOT set?
I've tried:
query_tags = Tags.query(Tags.new_prop == None).fetch()
But does not seem to get the data without that property set... Any suggestions?
Thanks!
The Datastore distinguishes between an entity that does not possess a property and one that possesses the property with a null value (None).
It is not possible to query for entities that are specifically lacking a given property. One alternative is to define a fixed (modeled) property with a default value of None, then filter for entities with None as the value of that property.
Sorry for probably simple question but I'm a newby in Django and really confused.
I have an ugly legacy tables that I can not change.
It has 2 tables:
class Salespersons(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
xsin = models.IntegerField()
name = models.CharField(max_length=200)
surname = models.CharField(max_length=200)
class Store(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
xsin = models.IntegerField()
brand = models.CharField(max_length=200)
So I suppose I can not add Foreign keys in class definitions because they change the tables.
I need to execute such sql request:
SELECT * FROM Salespersons, Store INNER JOIN Store ON (Salespersons.xsin = Store.xsin);
How can I achieve it using Django ORM?
Or I'm allowed to get Salespersons and Store separately i.e.
stores = Store.objects.filter(xsin = 1000)
salespersons = Salespersons.objects.filter(xsin = 1000)
Given your example query, are your tables actually named Salespersons/Store?
Anyway, something like this should work:
results = Salespersons.objects.extra(tables=["Store"],
where=["""Salespersons.xsin = Store.xsin"""])
However, given the names of the tables/models it doesn't seem to me that an inner join would be logically correct. Unless you always have just 1 salesperson per store with same xsin.
If you can make one of the xsin fields unique, you can use a ForeignKey with to_field to generate the inner join like this:
class Salespersons(models.Model):
xsin = models.IntegerField(unique=True)
class Store(models.Model):
xsin = models.ForeignKey(Salespersons, db_column='xsin', to_field='xsin')
>>> Store.objects.selected_related('xsin')
I don't see why you can't use the models.ForeignKey fields even if the database lacks the constraints -- if you don't explicitly execute the SQL to change the database then the tables won't change. If you use a ForeignKey then you can use Salespersons.objects.select_related('xsin') to request that the related objects are fetched at the same time.