I have a situation I don´t know how to model correctly. I want every child of a class to be associated with a media object (photo, video or music). I want to know which is the preffered approach to this problem. What I have right now is:
class Something(models.Model):
media = models.ForeignKey(Media)
class Media(models.Model):
title = models.CharField(max_lenght=100)
def get_tiny_object():
pass
def get_big_object():
pass
class Picture(Media):
picture = models.ImageField()
def get_tiny_object():
return ...
...
class Video(Media):
video = models.CharField(max_length=200) #youtube id
...
class Music(Media):
music = ....
You get the idea. ¿Does this work? Should I also record on "Something" what kind of media it is?
EDIT:
The idea behind having a Media class, is that I can render in the templates without knowing which kind of media I´m rendering. get_tiny_object() should return, if it is a picture:
"<img style="width:60px; height: 50px" ...>"
So if I have a foreign key to a media object lets say it's id=4, does django know that it should be fetched from music table, if the object I associated with is of Music kind? Because I´ll have 3 different id=4, one on each table (picture, video and music (and possibly more if the domain changes)).
I still think the question is a little hard to understand - the title is Database modelling in Django after all...However, this is a perfectly valid and reasonable thing to do, and seems to fit your intent:
The recommended way to do this is multi table inheritance - example:
class Media(models.Model):
pass
class Something(models.Model):
media = models.ForeignKey(Media)
class Picture(Media):
foo = models.CharField(max_length=100)
def get_tiny_object(self):
return self.foo
class Video(Media):
bar = models.CharField(max_length=100)
def get_tiny_object(self):
return self.bar
picture = Picture.objects.create(foo='some picture')
video = Video.objects.create(bar='some video')
something1 = Something.objects.create(media=picture)
something2 = Something.objects.create(media=video)
print something1.media.get_tiny_object() # this is a picture remember?
print something2.media.get_tiny_object() # and lo, here is a video
http://docs.djangoproject.com/en/dev/topics/db/models/#one-to-one-relationships
this says you should use a OneToOneField relating each Picture, Video, Music to a Media entry, and they all inherit directly from models.Model.
Also, you need to change your Something to
class Something(models.Model):
media = models.ForeignKey("Media")
because since Media isn't defined when you define Something (if you place it below it), it won't work, django provides you with the possibility of doing this with the classname instead of a reference to the class
Related
Problem
I need to display a gallery of images on a product page. This worked when we were at Django 1.6, but have since upgraded to Django 1.11 (Big Process). I am now stuck at how to get this to work within the new environment. Right now clicking Add Image brings up a pop up where I can select the image, and the regions associated with it (US, Canada, Spain, Etc..), but after clicking "Save" The popup title changes to Popup closing... and never closes - also the image is not added to the gallery. The image I upload itself IS added to the rest of the Images within filer, however it is not added to the ProductGallery Model.
What I've Got
Django: 1.11.7
Django-Filer: 1.2.7
Django-Suit: 0.2.25
Vanilla-Views: 1.0.4
I have Product models, these products have a many to many relationship to a ProductGallery model like so:
class Product(models.Model):
gallery = models.ManyToManyField('products.ProductGallery')
The ProductGallery is supposed to house Images and Videos allowing for upload of either, and providing one list to iterate through on the front end for display purposes.
The ProductGallery is defined as:
class ProductGallery(models.Model):
limit = models.Q(app_label='media', model='image') | models.Q(app_label='media', model='video')
order = models.PositiveIntegerField(default=0)
content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
object_id = models.PositiveIntegerField(db_index=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Meta:
ordering = ('order',)
def __str__(self):
return six.text_type(self.content_object)
where media.image is defined as: (I'll ignore video for the time being)
class Image(CountryInfoModel, models.Model):
image = FilerImageField(null=True, blank=True)
def __str__(self):
return str(self.image.name or self.image.original_filename)
I've got a view for Adding new Media like so:
class AddMedia(LoginRequiredMixin, StaffuserRequiredMixin, JsonRequestResponseMixin, GenericView):
require_json = True
def post(self, request, *args, **kwargs):
object_id = self.request_json["objectId"]
object_var = self.request_json["objectVarName"]
content_type_id = self.request_json["contentType"]
order = self.request_json["order"]
media_id = self.request_json["mediaId"]
media_type = self.request_json["mediaType"]
content_type = _get_content_type_or_404(content_type_id)
content_object = _get_object_or_404(content_type, object_id)
model_var = getattr(content_object, object_var)
try:
if media_type.lower() == "image":
obj = Image.objects.get(pk=media_id)
elif media_type.lower() == "video":
obj = Video.objects.get(pk=media_id)
else:
raise Http404("Invalid mediaType parameter: {0}".format(media_type))
media_item = model_var.create(content_object=obj)
media_item.order = order
media_item.save()
except model_var.model.DoesNotExist:
pass
return self.render_json_response({'message': "Order successfully updated"})
And I think thats all the pieces there are to this. I am lost on why when I click "save" the Image is not saved to the ProductGallery model at all. I'd be happy to provide more context if needed, and any help is very much appreciated.
Just in case anyone else comes across this and wants to know how it was fixed.
It turns out that some of the django-admin template functionality had been overwritten and was causing some issues.
Specifically this project had overwritten parts of the save button functionality. The function dismissAddRelatedObjectPopup used to be named dismissAddAnotherPopup
These functions were overwritten to provide the custom functionality outlined above with the ProductGallery. Django went to call the function, but this was throwing an error on the popup related to something called SelectBoxwhich then broke the ajax call that was needed to save the model correctly.
Hopefully this can help someone in the future!
I have a django model that I want to attach an extra piece of information to, depending on the environment the instance is in (which user is logged in). For this reason, I don't want to do it at the database level.
Is this okay to do? Or are there problems that I don't foresee?
in models.py
class FooOrBar(models.Model):
"""Type is 'foo' or 'bar'
"""
def __init__(self, type):
self.type = type
in views.py
class FooCheck(FooOrBar):
"""Never saved to the database
"""
def __init__(self, foo_or_bar):
self.__dict__ = foo_or_bar.__dict__.copy()
def check_type(self, external_type):
if external_type == 'foo':
self.is_foo = True
else:
self.is_foo = False
foos_or_bars = FooOrBar.objects.all()
foochecks = map(FooCheck, foos_or_bars)
for foocheck in foochecks:
foocheck.check_type('foo')
extra credit question: Is there a more efficient way of calling a method on multiple objects i.e. replacing the last forloop with something clever?
Okay, this does not work. Trying to delete a FooOrBar objects throws a complaint about
OperationalError at /
no such table: test_FooCheck
To get around this I'm just not going to inherit from FooOrBar, but if anyone has a suggestion on a better way to do it I'd be interested in hearing it
I had a similar issue, I did something like:
class Foo(models.Model):
# specific info goes here
class Bar(models.Model):
# specific info goes here
class FooBar(models.Model):
CLASS_TYPES = {
"foo":Foo,
"bar":Bar
}
type = models.CharField(choices=CLASS_TYPES)
id = models.IntegerField()
#field to identify FooBar
then you can get the object back using
object = FooBar.CLASS_TYPES[instance.type].objects.get(id=instance.id)
where instance is the FooBar instance
I'm doing a simple program about customers, products and drafts.
Since they are referenced to each other in some way, when I delete one entity of a kind, another entity of another kind might give an error.
Here's what I have:
-customer.py
class Customer(db.Model):
"""Defines the Customer entity or model."""
c_name = db.StringProperty(required=True)
c_address = db.StringProperty()
c_email = db.StringProperty() ...
-draft.py
class Draft(db.Model):
"""Defines the draft entity or model."""
d_customer = db.ReferenceProperty( customer.Customer,
collection_name='draft_set')
d_address = db.StringProperty()
d_country = db.StringProperty() ...
Ok, now what I want to do is check if a customer has any Draft referencing to him, before deleting him.
This is the code I'm using:
def deleteCustomer(self, customer_key):
'''Deletes an existing Customer'''
# Get the customer by its key
customer = Customer.get(customer_key)
if customer.draft_set: # (or customer.draft_set.count > 0...)
customer.delete()
else:
do_something_else()
And now, it comes the problem.
If I have a draft previously created with the selected customer on it, there's no problem at all, and it does what has to do. But if I haven't created any draft that references to that customer, when trying to delete him, it will show this error:
AttributeError: 'Customer' object has no attribute 'draft_set'
What am I doing wrong? Is it needed to always create a Draft including a Customer for him to have the collection_name property "available"?
EDIT: I found out what the error was.
Since I have both classes in different .py files, it seems that GAE loads the entities into the datastore at the same moment as it "goes through" the file that contains that model.
Therefore, if I'm executing the program, and never use or import that file, the datastore is not updated until then.
Now what I'm doing is:
from draft.py import Draft
inside de "deleteCustomer()" function and it's finally working fine, but I get a horrible "warning not used" because of so.
Is there any other way I can fix this?
The collection_name property a query, so it should always be available.
What you may be missing is the reference_class parameter (check the ReferenceProperty docs)
class Draft(db.Model):
"""Defines the draft entity or model."""
d_customer = db.ReferenceProperty(reference_class=customer.Customer, collection_name='draft_set')
The following should work:
if customer.draft_set.count():
customer.delete()
note that customer.draft_set will always return true, as it is the generated Query object, so you MUST use the count()
There were two possible solutions:
Ugly, bad one: as described in my edited question.
Best practice: put all the models together inside one file (e.g. models.py) that looks like this:
class Customer(db.Model):
"""Defines the Customer entity or model."""
c_name = db.StringProperty(required=True)
c_address = db.StringProperty()
c_email = db.StringProperty() ...
class Draft(db.Model):
"""Defines the draft entity or model."""
d_customer = db.ReferenceProperty( customer.Customer,
collection_name='draft_set')
d_address = db.StringProperty()
d_country = db.StringProperty() ...
Easy!
In my schema, as described in the below test data generation example, I want to know a good way to:
Dereference all instances of Favourites that have reference keys to instances of Pictures that have been deleted. Just delete any Favourite that links to a deleted picture.
The Person class is a user
The Picture class is something that can be a Favourite
The Favourite class is an example of the Link-Model way of having many-to-many relationships.
Why this question?
First I hope it doesn't fall out of the scope here, second because this can happen and third because it's interesting.
How?
Let's say that a person can have up to thousands favourites, something like Likes are on social networks or to make it worse, orders, accounts or invalid data in a scientific application.
In our example for some reason (and these reasons happen) a person is experiencing lot of dead favourite link, or I do know, that there are dead favourites.
What would be a good way to do this, reducing ndb.get() operations and not iterating through every Favourite.
Lets not complicate things. Lets make the assumption that we have only one user suffering from dead favourites. He has a class of Person and stubbed user_id property of '123'.
In the following example you can use the following handlers and their corresponding functions.
import time
import sys
import logging
import random
import cgi
import webapp2
from google.appengine.ext import ndb
class Person(ndb.Expando):
pass
class Picture(ndb.Expando):
pass
class Favourite(ndb.Expando):
user_id = ndb.StringProperty(required=True)
#picture = ndb.KeyProperty(kind=Picture, required=True)
pass
class GenerateDataHandler(webapp2.RequestHandler):
def get(self):
try:
number_of_models = abs(int(cgi.escape(self.request.get('n'))))
except:
number_of_models = 10
logging.info("GET ?n=parameter not defined. Using default.")
pass
user_id = '123' #stub
person = Person.query().filter(ndb.GenericProperty('user_id') == user_id).get()
if not person:
person = Person()
person.user_id = user_id #Stub
person.put()
logging.info("Created Person instance")
if not self._gen_data(person, number_of_models):
return
self.response.write("Data generated successfully")
def _gen_data(self, person, number_of_models):
first, last = Picture.allocate_ids(number_of_models)
picture_keys = [ndb.Key(Picture, id) for id in range(first, last+1)]
pictures = []
favourites = []
for picture_key in picture_keys:
picture = Picture(key=picture_key)
pictures.append(picture)
favourite = Favourite(parent=person.key,
user_id=person.user_id,
picture=picture_key
)
favourites.append(favourite)
entities = favourites
entities[1:1] = pictures
ndb.put_multi(entities)
return True
class CorruptDataHandler(webapp2.RequestHandler):
def get(self):
if not self._corrupt_data(0.5):#50% corruption
return
self.response.write("Data corruption completed successfully")
def _corrupt_data(self, n):
picture_keys = Picture.query().fetch(99999, keys_only=True)
random_picture_keys = random.sample(picture_keys, int(float(len(picture_keys))*n))
ndb.delete_multi(random_picture_keys)
return True
class FixDataHandler(webapp2.RequestHandler):
def get(self):
user_id = '123' #stub
person = Person.query().filter(ndb.GenericProperty('user_id') == user_id).get()
self._dereference(person)
def _dereference(self, person):
#Here if where you implement your answer
Separate handlers due to eventual consistency in
the NDB Datastore. More info:
GAE put_multi() entities using backend NDB
Of course I am posting an answer as well to show that I tried something before posting this.
A ReferenceProperty is just a key, so if you have the key of the deleted Person, you can use that to query the Favourite.
Otherwise, there's no easy way. You'll have to filter through all Favourites and find ones that have an invalid Picture. It's very simple in a mapreduce job, but could be an expensive query if you have a lot of Favourites.
You could use a pre delete hook (look here for a way to implement it)
Of course this could be done easier if you use the NDB API instead of the Datastore API (hooks on NDB), but then you'll have to change the way you make the referenes
Given the following models:
class Module(models.Model):
pass
class Content(models.Model):
module = models.ForeignKey(Module, related_name='contents')
class Blog(Module):
pass
class Post(Content):
pass
I would like to be able to get all the "post" objects owned by blog doing something like:
b = Blog.objects.get(pk=1)
b.posts.all()
However, I haven't figured out a good way of doing this. I can't use b.contents.all() as I need Post instances and not Content instances. I won't ever have a root content object, every content object is going to be subclassed, but I can't use abstract classes as I want a central table with all my content in it and then there will be content_blog etc tables for all the unique inherited pieces of content.
I also tried doing this
class Content(models.Model):
module = models.ForeignKey(Module, related_name='%(class)')
but that failed miserably as far as I could tell.
The simplest way might add a method to Blog model to return a Post queryset, like this:
class Blog(Module):
def _get_posts(self):
return Post.objects.filter(module=self)
posts = property(_get_posts)
The problem is you have to add method for every sub-model. The related_name seems only works for abstract base class.
This solution comes to my mind:
# ...
class Blog(Module):
#property
def posts(self):
return self.contents
class Post(Content):
pass
This way, doing blog.posts is the same as doing blog.contents:
>>> blog = Blog.objects.get(pk=1)
>>> blog.posts.all()
# [ ... ]