So lets say we have this model:
class Student(models.Model):
am = models.SmallIntegerField(unique=True, primary_key=True) # XXX: max_value = 10000
date_enrolled = models.DateField('Date Enrolled')
semester = models.IntegerField(default=1)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
undergraduate = models.BooleanField(default=True)
An update view like this:
class StudentUpdateView(SqlPresenterMixin, StudentMixin, UpdateView):
model = Student
form_class = StudentForm
template_name = "profapp/student_form.html"
slug_field = "am"
Then this test fails:
class TestStudent(TestCase):
def setUp(self):
self.c = Client()
Student.objects.create(am=2222, first_name="Chris",
last_name="Perivolas",
date_enrolled=datetime.date(year=2010, day=15,
month=2))
Student.objects.create(am=7362, first_name="Mary",
last_name="Karagewrgena",
date_enrolled=datetime.date(year=2010, day=15,
month=2))
def test_update(self):
"""
"""
r = self.c.post("/profapp/students/2222/update/",
dict(am=7363, first_name="Chris",
last_name="Perivolas",
date_enrolled="3/15/2010",
semester=2,
undergraduate=1))
self.assertEquals(Student.objects.filter(am=2222).exists(), False)
In short update view doesn't delete the old entry when updating a primary key. What is the best way of solving this?
According to Django documentation, PK should never change. PK is what ties your object to a specific row in the DB. When you change the PK, Django loses the connection to the original row in the DB and assumes that you want to create another row.
You should add another field to act as changeable id if you really need it.
Related
Suppose in a relational database schema we have a student, a subject and a teacher which connect to each other with a relation teaches. Also, the relation has an attribute time that stores the time of the lesson. This is the most complete yet simplified example I can think to describe my case. Now, the most pythonic and django-wise way I can think of trying to reach a correct solution is, after creating a model class for student, subject and teacher, to create a new class Teaches, which has the foreign keys for the three other classes; also it has the property date field for time. This class would look something like this:
class Teaches(models.Model):
teachers = models.ForeignKey(Teacher, on_delete_models.CASCADE)
subjects = models.ForeignKey(Subject, on_delete_models.CASCADE)
students = models.ForeignKey(Student, on_delete_models.CASCADE)
time = models.DateField
class Meta:
constraints = [
fields=['teachers', 'subjects', 'students']
name='teacher_subject_student_triplet'
]
I added the Meta class because this is what this answer recommends as the correct approach.
The problem is that that in the migrations file I can still see the id field. The only way I've seen there is to remove it is to set another field as Primary Key, but in my case I cannot do that, having more than one keys. Any suggestions?
=========== model.py =============
from django.db import models
class TeacherModel(models.Model):
teacher_code = models.CharField(max_length=255)
def __str__(self):
return self.teacher_code
class SubjectModel(models.Model):
subject_code = models.CharField(max_length=255)
def __str__(self):
return self.subject_code
class StudentModel(models.Model):
student_code = models.CharField(max_length=255)
def __str__(self):
return self.student_code
class Teaches(models.Model):
custom_primary_key = models.SlugField(primary_key=True,blank=True)
teacher = models.ForeignKey(TeacherModel, on_delete=models.CASCADE)
subject = models.ForeignKey(SubjectModel, on_delete=models.CASCADE)
student = models.ForeignKey(StudentModel, on_delete=models.CASCADE)
time = models.DateField
#property
def make_key(self):
new_key = str(self.teacher.teacher_code + self.subject.subject_code + self.student.student_code)
return new_key
def save(self, *args, **kwargs):
self.custom_primary_key = self.make_key
super(Teaches, self).save(*args, **kwargs)
========= Output ==============
You can remove autogenerated id by adding primary_key=True, see below code:
class Person(models.Model):
username = CharField(primary_key=True, max_length=100)
first_name = CharField(null=True, blank=True, max_length=100)
setting a field to primary_key=True automatically makes it unique and not null.
In settings.py:
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Controls the automatic generation of primary keys of each model if defined in settings.
Read this article:
Set AutoField or BigAutoField on a per model basis
The goal of this project is to create an API that refreshes hourly with the most up to date betting odds for a list of games that I'll be scraping hourly from the internet. The goal structure for the JSON returned will be each game as the parent object and the nested children will be the top 1 record for each of linesmakers being scraped by updated date. My understanding is that the best way to accomplish this is to modify the to_representation function within the ListSerializer to return the appropriate queryset.
Because I need the game_id of the parent element to grab the children of the appropriate game, I've attempted to pull the game_id out of the data that gets passed. The issue is that this line looks to be populated correctly when I see what it contains through an exception, but when I let the full code run, I get a list index is out of range exception.
For ex.
class OddsMakerListSerializer(serializers.ListSerializer):
def to_representation(self, data):
game = data.all()[0].game_id
#if I put this here it evaluates to 1 which should run the raw sql below correctly
raise Exception(game)
data = OddsMaker.objects.filter(odds_id__in = RawSQL(''' SELECT o.odds_id
FROM gamesbackend_oddsmaker o
INNER JOIN (
SELECT game_id
, oddsmaker
, max(updated_datetime) as last_updated
FROM gamesbackend_oddsmaker
WHERE game_id = %s
GROUP BY game_id
, oddsmaker
) l on o.game_id = l.game_id
and o.oddsmaker = l.oddsmaker
and o.updated_datetime = l.last_updated
''', [game]))
#if I put this here the data appears to be populated correctly and contain the right data
raise Exception(data)
data = [game for game in data]
return data
Now, if I remove these raise Exceptions, I get the list index is out of range. My initial thought was that there's something else that depends on "data" being returned as a list, so I created the list comprehension snippet, but that doesn't resolve the issue.
So, my question is 1) Is there an easier way to accomplish what I'm going for? I'm not using a postgres backend so distinct on isn't available to me. and 2) If not, its not clear to me what instance is that's being passed in or what is expected to be returned. I've consulted the documentation and it looks as though it expects a dictionary and that might be part of the issue, but again the error message references a list. https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
I appreciate any help in understanding what is going on here in advance.
Edit:
The rest of the serializers:
class OddsMakerSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = OddsMakerListSerializer
model = OddsMaker
fields = ('odds_id','game_id','oddsmaker','home_ml',
'away_ml','home_spread','home_spread_odds',
'away_spread_odds','total','total_over_odds',
'total_under_odds','updated_datetime')
class GameSerializer(serializers.ModelSerializer):
oddsmaker_set = OddsMakerSerializer(many=True, read_only=True)
class Meta:
model = Game
fields = ('game_id','date','sport', 'home_team',
'away_team','home_score', 'away_score',
'home_win','away_win', 'game_completed',
'oddsmaker_set')
models.py:
class Game(models.Model):
game_id = models.AutoField(primary_key=True)
date = models.DateTimeField(null=True)
sport=models.CharField(max_length=256, null=True)
home_team = models.CharField(max_length=256, null=True)
away_team = models.CharField(max_length=256, null=True)
home_score = models.IntegerField(default=0, null=True)
away_score = models.IntegerField(default=0, null=True)
home_win = models.BooleanField(default=0, null=True)
away_win = models.BooleanField(default=0, null=True)
game_completed = models.BooleanField(default=0, null=True)
class OddsMaker(models.Model):
odds_id = models.AutoField(primary_key=True)
game = models.ForeignKey('Game', on_delete = models.CASCADE)
oddsmaker = models.CharField(max_length=256)
home_ml = models.IntegerField(default=999999)
away_ml = models.IntegerField(default=999999)
home_spread = models.FloatField(default=999)
home_spread_odds = models.IntegerField(default=9999)
away_spread_odds = models.IntegerField(default=9999)
total = models.FloatField(default=999)
total_over_odds = models.IntegerField(default=999)
total_under_odds = models.IntegerField(default=999)
updated_datetime = models.DateTimeField(auto_now=True)
views.py:
class GameView(viewsets.ModelViewSet):
queryset = Game.objects.all()
serializer_class = GameSerializer
Thanks
To answer the question in the title:
The instance being passed to the Serializer.to_representation() is the instance you pass when initializing the serializer
queryset = MyModel.objects.all()
Serializer(queryset, many=True)
instance = MyModel.objects.all().first()
Serializer(data)
Usually you don't have to inherit from ListSerializer per se. You can inherit from BaseSerializer and whenever you pass many=True during initialization, it will automatically 'becomeaListSerializer`. You can see this in action here
To answer your problem
from django.db.models import Max
class OddsMakerListSerializer(serializers.ListSerializer):
def to_representation(self, data): # data passed is a queryset of oddsmaker
# Do your filtering here
latest_date = data.aggregate(
latest_date=Max('updated_datetime')
).get('latest_date').date()
latest_records = data.filter(
updated_date_time__year=latest_date.year,
updated_date_time__month=latest_date.month,
updated_date_time__day=latest_date.day
)
return super().to_representation(latest_records)
In my postgressql database I have tables:
class Topic(models.Model):
Definition = models.TextField(default='Definition')
Name = models.TextField(default='Name')
def __str__(self):
return self.Name
class Question(models.Model):
Statement = models.TextField(default='Question')
def __str__(self):
return self.Statement
class Planit_location(models.Model):
Planit_location = models.CharField(max_length=255, default='Planit_location')
def __str__(self):
return self.Planit_location
class ClientDetail(models.Model):
Sector = models.ForeignKey(Sector, on_delete=models.CASCADE)
Client_name = models.CharField(max_length=255, default='Client_name')
def __str__(self):
return self.Client_name
class Response(models.Model):
Question = models.ForeignKey(Question, on_delete=models.CASCADE)
Topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
Response = models.TextField(default='Response')
Client = models.ForeignKey(ClientDetail, on_delete=models.CASCADE)
Planit_Location = models.ForeignKey(Planit_location, on_delete=models.CASCADE)
Image = models.ForeignKey(Image, on_delete=models.CASCADE)
def __str__(self):
return self.Response
I want to create a modelform using all these tables so I can add new questions and responses to my database, which are then linked to a topic, location and client (these 3 will be a dropdownlist from data in db).
I have managed to create a modelform for just question and response but when I try to submit it I get "null value in column "Question_id" violates not-null constraint"
Here is the code:
if request.method == 'POST':
qform = QuestionForm(request.POST)
rform = ResponseForm(request.POST)
if qform.is_valid() and rform.is_valid():
qf = qform.save()
rf = rform.save()
return render(request, 'app/adddatatest.html', {
"qform": QuestionForm(),
"rform": ResponseForm(),
})
After checking for is_valid() in view do this:
qf = qform.save() # Goes to database
rf = rform.save(commit=False) # Doesn't goes to database
rf.Question = qf # gets required attribute
rf.save() # then goes to database
You can't save Response object without specifying the the foreign key Question. So in rform.save pass argument commit=False to not actually saving it in database yet. Then add the value for the foreign key to the new created Response object, foreign key is required otherwise you will get IntegrityError. Then finally save it to the database.
I'm very confused about this right now,
so I know when there's a simple code like the below
def text_detail(request ,course_pk, step_pk):
step = get_object_or_404(Text, course_id = course_pk, pk=step_pk)
course_pk and step_pk from the url, and those requests are set equal to course_id and pk here. but what I don't understand is what is course_id and pk here? I mean, course_id is from Course model which is foreignkey to step. so it's self.Course.id so it's course_id. But then, how about the next one pk? shouldn't it be step_id = step_pk? when it's just pk how does django know which pk it is?
Sorry if the question is very confusing, I'm very confused right now.
Edit
class Step(models.Model):
title = models.CharField(max_length=200)
description = models.CharField()
order = models.IntegerField(default=0)
course = models.ForeignKey(Course)
class Meta:
abstract = True
ordering = ['order',]
def __str__(self):
self.title
class Text(Step):
content = models.TextField(blank=True, default="")
Actually the get_or_404() method doing a similar/exact job as below,
try:
return Text.object.get(pk=step_pk,course_id = course_pk)
except Text.DoesNotExist:
raise Http404
You can read the source code of the same here
What is course_id and pk ?
Both are attributes of your Text model, as the name indicates pk is your Primary Key of Text model and course_id is the id/pk of course field which is a FK.
EDIT
Text is inherited from Step model so, it will show properties of usual python class.Hence, the Text model be like this internally (not-exact)
class Text(models.Model):
content = models.TextField(blank=True, default="")
title = models.CharField(max_length=200)
description = models.CharField()
order = models.IntegerField(default=0)
course = models.ForeignKey(Course)
class Meta:
ordering = ['order', ]
def __str__(self):
return self.title
Example
text = Text.objects.get(id=1) # text instance with id=1
text.course_id # will hold the id of "course" instance which is related to the particular "text" instance
URL assignment and all those stuffs are entirely depends on your choice and logic. So If you need to get a Text instance in your view, do as below,
text = get_object_or_404(Text, pk = pk_of_TEXT_instance)
I have following model (models.py) in my Django project:
class Topic(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=140)
def __unicode__(self):
return self.title
class ArgumentElement(models.Model):
id = models.AutoField(primary_key=True)
contents = models.CharField(max_length=256)
elementType = models.CharField(max_length=10)
topic = models.ForeignKey(Topic, related_name='ArgumentElement_Topic')
def __unicode__(self):
return self.contents
class ArgumentElementConnection(models.Model):
id = models.AutoField(primary_key=True)
sourceId = models.ForeignKey(ArgumentElement, related_name='connection_source')
targetId = models.ForeignKey(ArgumentElement, related_name='connection_target')
topic = models.ForeignKey(Topic, related_name='ArgumentElementConnection_Topic')
connectionType = models.CharField(max_length=7)
def __unicode__(self):
return self.id
I add all three models to the admin (admin.py):
from django.contrib import admin
from history_site.opinions.models import ArgumentElement, ArgumentElementConnection, Topic
admin.site.register(ArgumentElement, admin.ModelAdmin)
admin.site.register(ArgumentElementConnection, admin.ModelAdmin)
admin.site.register(Topic, admin.ModelAdmin)
When I create an instance of Topic and then try to delete it in the Admin, I get the error no such column: opinions_argumentelement.topic_id.
What's wrong with my models.py?
It seems that AutoFields using the sqllite3 backend don't increment properly. Is there any reason you are including the line id = models.AutoField(primary_key=True) ? If you leave it out, an auto-increment primary key field will be automatically added and is more likely to be correctly created. Try deleting that line and creating a new sqllite database file.