Say object contains multiple types of objects (Models) in an array, like a Page, that could contain a Post, Blog, Picture, in any quantity or order, where the user scrolls down and sees the following element and that depending on the page, is next element one of these types of elements. Thus a Page could contain:
A Post
A Post
A Picture
Or something pretty different such as:
A Picture
A Blog
A Picture
An element could be an abstract entity that has an attribute as a ForeignKey relating itself with Page, and its inheritance would gain access to that relationship:
class Page(models.Model):
...
class PageElement(models.Model):
page = models.ForeignKey(Page, verbose_name="Page", on_delete=CASCADE)
class Meta:
abstract = True
class Post(PageElement):
...
class Blog(PageElement):
...
class Picture(PageElement):
...
With this approach, you can serialize Page with a REST serializer, but you wouldn't have access to an pageElement_set, instead you would need to call post_set, blog_set, picture_set and loose the order of the objects.
By just adding a related_name key in the ForeignKey, we get an error:
pages.Picture.page: (fields.E305) Reverse query name for 'Picture.page' clashes with reverse query name for 'Post.page'.
HINT: Add or change a related_name argument to the definition for 'Picture.page' or 'Post.page'.
The question is: How do you implement such relationships in Django?
So that you can have an order set of PageElement instances, and be able to have the desired order.
Since you want to query multiple Models in order and you can see that your models are in Model is a PageElement relationship, then instead of inheriting from an abstract Model you need to have a OneToOneField with a common model:
class PageElement(models.Model):
POST = 'PO'
BLOG = 'BL'
PICTURE = 'PI'
ELEMENT_TYPE_CHOICES = [
(POST, 'Post'),
(BLOG, 'Blog'),
(PICTURE, 'Picture'),
]
element_type = models.CharField(
max_length=2,
choices=ELEMENT_TYPE_CHOICES,
default=POST,
)
# common fields
class Post():
page_element = models.OneToOneField(
PageElement,
on_delete=models.CASCADE,
related_name = "post"
)
class Blog():
page_element = models.OneToOneField(
PageElement,
on_delete=models.CASCADE,
related_name = "blog"
)
class Picture():
page_element = models.OneToOneField(
PageElement,
on_delete=models.CASCADE,
related_name = "picture"
)
You can inherit from a common abstract class that has the OneToOneField for ease if needed.
Related
As stated in this question
With Django REST Framework, a standard ModelSerializer will allow ForeignKey model relationships to be assigned or changed by POSTing an ID as an Integer.
I am attempting to update a reverse relationship of the following format:
class Lesson(models.Model):
name = models.TextField()
class Quiz(models.Model):
lesson = models.ForeignKey(Lesson, related_name='quizzes', null=True)
class LessonSerializer(serializers.ModelSerializer):
quizzes = serializers.PrimaryKeyRelatedField(queryset=Quiz.objects.all(), many=True, write_only=True)
class Meta:
model = Lesson
fields = ('quizzes')
When posting an update containing an array of quiz primary keys using LessonSerializer I get TypeError: 'Quiz' instance expected, got '1'.
Is it possible to assign or change a reverse relationship by POSTing an array of primary keys?
You don't need special field in a serializer, DRF serializers is smart enough to figure out related field from a fields value.
class LessonSerializer(serializers.ModelSerializer):
class Meta:
model = Lesson
fields = ('quizzes', 'name')
And you should pass list of ids, if there is only one value it should be a list anyway.
To solve this you need to create a Quiz instance first before you assign it to Lesson. Below's a small change to your code.
class Lesson(models.Model):
name = models.TextField()
class Quiz(models.Model):
lesson = models.ForeignKey(Lesson, related_name='quizzes', null=True)
class LessonSerializer(serializers.ModelSerializer):
quizzes = serializers.PrimaryKeyRelatedField(queryset=Quiz.objects.all(), many=True, write_only=True)
class Meta:
model = Lesson
fields = ('quizzes')
class QuizSerializer(serializers.ModelSerializer):
class Meta:
model = Quiz
fields = ('name')
Create POST request with with quiz then run your post again
I'm new to Django and I've read many topics about authentication but I can't still figure out what is the best (or most used) way to set more account types in Django to be able to make different registrations.
Real Example:
I want to create a web page which is dedicated to provide communication and other services between customers and sellers (in this case people who translate across many languages).
The main point is this:
The web should be of course different for those two types of users.
Customer should not have to fill in forms like languages or price per hour during registration.
Another thing is that Seller should have access to pages like "Opened jobs" etc. and Customer should have access to pages like "Estimate price", "Upload text to translate" and many others.
I've already these models:
class Language(models.Model):
shortcut = models.CharField(max_length=6)
name = models.CharField(max_length=50)
price_per_sign = models.FloatField()
class BaseUser(AbstractUser):
# username = models.CharField(max_length=50)
# email = models.EmailField(unique=True)
# first_name = models.CharField(max_length=50)
# surname = models.CharField(max_length=50)
telephone = models.CharField(max_length=50)
class TranslatorUser(BaseUser):
languages = models.ManyToManyField(Language)
class Meta:
verbose_name = 'Translator'
verbose_name_plural = 'Translators'
class CustomerUser(BaseUser):
spent = models.FloatField()
class Meta:
verbose_name = 'Customer'
verbose_name_plural = 'Customers'
How to create a registration forms for both Customer and Translator?
This is my forms.py:
class TranslatorUserRegistrationForm(UserCreationForm):
username = forms.CharField(required=True)
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ['email','username']
def save(self, commit=True):
user = super(TranslatorUserRegistrationForm,self).save(commit=True)
translator = TranslatorUser(user=user,email=self.cleaned_data['email'],username=self.cleaned_data['username'])
translator.save()
return user, translator
And views.py:
def register_translator(request):
form = TranslatorUserRegistrationForm()
if request.method==['POST']:
form = TranslatorUserRegistrationForm(request.POST)
else: return render(request,'registration/register_form.html',{'registration_form':form})
When I try to make migrations, it returns this error:
ERRORS:
auth.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with
reverse accessor for 'BaseUser.groups'.
HINT: Add or change a related_name argument to the definition for 'User.
groups' or 'BaseUser.groups'.
auth.User.user_permissions: (fields.E304) Reverse accessor for 'User.user_permis
sions' clashes with reverse accessor for 'BaseUser.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'User.
user_permissions' or 'BaseUser.user_permissions'.
auth_test.BaseUser.groups: (fields.E304) Reverse accessor for 'BaseUser.groups'
clashes with reverse accessor for 'User.groups'.
HINT: Add or change a related_name argument to the definition for 'BaseU
ser.groups' or 'User.groups'.
auth_test.BaseUser.user_permissions: (fields.E304) Reverse accessor for 'BaseUse
r.user_permissions' clashes with reverse accessor for 'User.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'BaseU
ser.user_permissions' or 'User.user_permissions'.
Solution that I'm using is based on Profile models. Those are models that will extend normal user model (by one to one relation) and won't contain authentication data. That type of models are described in django docs, but they are made simply by OneToOneField.
I'm using solution based on model inheritance. In django models that aren't abstract also can be inherited, django will under the hood create OneToOneField in child model pointing to parent model. That field will also be used as an primary key, so ID of child model will always be same as it's parent. Also, you will be able to access parent fields from child models (like they we're here!).
So in other words, you should create 2 models (one for each type of user) that will inherit from standard User model (or customized one, but it can't be abstract) and for each registered user you will create object from one of that models.
For easy use, you can create middleware that will automatically detect type of user and swap user model in request object to specialized one.
Is there a way in Django to have multiple objects stored and manageable (in Django admin) inside another object?
Example, I have two models: Items and RMA. The RMA may have multiple Items inside of it. Each Item is unique in the sense that it is an inventoried part, so I can't just reference the same item multiple times with foreignKey (though maybe I'm misunderstanding its use/implementation).
So for now, I have an Item model:
class Item(models.Model):
serial_number = models.CharField(max_length=200)
name = models.CharField(max_length=200)
part_number = models.CharField(max_length=200)
location = models.CharField(max_length=200)
def __unicode__(self):
return self.name
And an RMA model:
class RMA(models.Model):
number = models.AutoField(primary_key=True)
items = ?????
Ultimately I'd like to be able to maintain use of the Django admin functionality to add/remove items from an RMA if necessary, so I've been staying away from serializing a list and then deserializing on display. Any help would be much appreciated.
You're modeling a has-many relationship.
This would be modeled with a Foreign Key on Item to RMA:
class Item(models.Model):
serial_number = models.CharField(max_length=200)
name = models.CharField(max_length=200)
part_number = models.CharField(max_length=200)
location = models.CharField(max_length=200)
rma = models.ForeignKey(RMA)
def __unicode__(self):
return self.name
To make it accessible in the admin of RMA you need djangos InlineAdmin functionality.
You can find examples in the django tutorial part2.
You are effectively describing a Many-To-One relation and to do this you are going to have to add the ForeignKey reference to the Item model, not to the RMA model.
You can also add a related_name to give the RMA model an attribute that you can call.
For example:
class Item(models.Model):
rma = models.ForeignKey(RMA,related_name="items")
serial_number = models.CharField(max_length=200)
# etc...
To manage the creation of these, you'll need an InlineModelAdmin form, so your admin.py file will need to look like this:
from django.contrib import admin
class ItemInline(admin.TabularInline):
model = Item
class RMAAdmin(admin.ModelAdmin):
inlines = [
ItemInline,
]
When I syncdb, I get many errors like this:
transcription.transcription1: Accessor for field 'participant_content_type' clashes with related field 'ContentType.auxi
liary_model_as_participant'. Add a related_name argument to the definition for 'participant_content_type'.
transcription.transcription1: Reverse query name for field 'participant_content_type' clashes with related field 'Conten
tType.auxiliary_model_as_participant'. Add a related_name argument to the definition for 'participant_content_type'.
My models already have related names:
# my base class which I intend to inherit from in many places.
# Many types of AuxiliaryModel will point at participant/match objects.:
class AuxiliaryModel(models.Model):
participant_content_type = models.ForeignKey(ContentType,
editable=False,
related_name = 'auxiliary_model_as_participant')
participant_object_id = models.PositiveIntegerField(editable=False)
participant = generic.GenericForeignKey('participant_content_type',
'participant_object_id',
)
match_content_type = models.ForeignKey(ContentType,
editable=False,
related_name = 'auxiliary_model_as_match')
match_object_id = models.PositiveIntegerField(editable=False)
match = generic.GenericForeignKey('match_content_type',
'match_object_id',
)
class Meta:
abstract = True
class Transcription(AuxiliaryModel):
transcription = models.TextField(max_length=TRANSCRIPTION_MAX_LENGTH,
null=True)
class Meta:
abstract = True
class Transcription1(Transcription):
pass
class Transcription2(Transcription):
pass
class Transcription3(Transcription):
pass
The problem goes away when I comment out Transcription2 and Transcription3, so it seems like the related_names clash. Do I have to make them unique? If so, is there a way to do this without having to write boilerplate code in each child class?
From the Django docs https://docs.djangoproject.com/en/dev/topics/db/models/#be-careful-with-related-name :
If you are using the related_name attribute on a ForeignKey or ManyToManyField, you must always specify a unique reverse name for the field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including related_name) each time.
To work around this problem, when you are using related_name in an abstract base class (only), part of the name should contain '%(app_label)s' and '%(class)s'.
In this case, I think this will work:
participant_content_type = models.ForeignKey(ContentType,
editable=False,
related_name = '%(app_label)s_%(class)s_as_participant')
match_content_type = models.ForeignKey(ContentType,
editable=False,
related_name = '%(app_label)s_%(class)s_model_as_match')
So, using %(app_label)_transcription2_as_participant you can access the reverse of Transcription2.participant_content_type
In the admin I want to use inline elements. I want category to display
the items it is related to.
But I get this error:
Exception at /admin/store/category/7/
<class 'store.models.Item'> has no ForeignKey to
<class 'store.models.Category'>
It's true, of-course, since I chose to use Category to point to the
items it has.
But, how can I get the admin to list in-line all the items that a
given Category has?
How can I get around this error?
CONTEXT:
class Category:
items=models.ManyToManyField(Item,through='Categoryhasitem')'
class Categoryhasitem(models.Model):
category = models.ForeignKey(Category, db_column='category')
item = models.ForeignKey(Item, db_column='item')
class Item(models.Model):
id = models.AutoField(primary_key=True)
This is my admin.py file.
class ItemInline(admin.TabularInline):
model=Item
class CategoryAdmin(admin.ModelAdmin):
inlines=[ItemInline,]
class ItemAdmin(admin.ModelAdmin):
pass
admin.site.register(Category, CategoryAdmin)
admin.site.register(Item, ItemAdmin)
The syntax is slightly different to display many-to-many relations using an inline.
class ItemInline(admin.TabularInline):
model = Category.items.through
class CategoryAdmin(admin.ModelAdmin):
inlines = [
ItemInline,
]
exclude = ('items',)
See the django admin docs for working with many-to-many models for more details.