1/n relation

Django, Flask, Bottle, WSGI, CGI…
Antworten
robin_
User
Beiträge: 48
Registriert: Montag 3. August 2020, 17:59

Hi,

ich möchte folgendes "Konstrukt" ans laufen kriegen. Ich habe die Model-Klasse Task und jedes Objekt davon soll zwischen 0 und n Objekte von Task_item halten.
Die Models sehen so aus:

Code: Alles auswählen

from django.db import models

# Create your models here.
class Task(models.Model):
	name = models.CharField(max_length=250, null=True)
	created = models.DateTimeField(auto_now_add=True, null=True)

	class Meta:
		ordering = ('name', )

	def __str__(self):
		formatedDate = self.created.strftime("%Y-%m-%d %H:%M:%S")
		return self.name + " " + formatedDate

class Task_item(models.Model):
	name = models.CharField(max_length=250, null=True)
	cost = models.IntegerField(default=0, null=True)
	price = models.IntegerField(default=10, null=True)
	task = models.ForeignKey(Task, on_delete=models.CASCADE, null=True, name='task_item')

	class Meta:
		ordering = ('name', )
	
	def __str__(self):
		return self.name 
Dazu hab ich den Serializer erweitert:

Code: Alles auswählen

class TaskSerializer(serializers.ModelSerializer):
	class Meta:
		model = Task
		fields = ('id','name','created')


class Task_itemSerializer(serializers.ModelSerializer):
	class Meta:
		model = Task_item 
		fiels = ('id', 'name', 'cost', 'price', 'task')
Hier die urls:

Code: Alles auswählen

urlpatterns = [
    path('', views.task_list, name='task_list'),
    path('<int:pk>/', views.task_detail, name='task_detail'),
    path('<int:pk>/items/', views.task_items, name='task_items'),
    
]	
Wenn ich also auf /tasks/ gehe, wird task_list ausgeführt. Mit /tasks/2/ komme ich auf die entsprechende task_detail View.

Ich möchte, dass wenn ich /tasks/2/items per GET/ POST aufrufe, die dem Task 2 zugeordneten "Task_item"-Objekte ausgespuckt werden. Hier hängts.

ich kriege aktuell folgenden Fehler:

Code: Alles auswählen

Internal Server Error: /tasks/2/items/
Traceback (most recent call last):
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\django\core\handlers\base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\django\views\generic\base.py", line 73, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\rest_framework\views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\rest_framework\views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\rest_framework\views.py", line 476, in raise_uncaught_exception
    raise exc
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\rest_framework\views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "C:\Users\Robin\craftorgaproj\lib\site-packages\rest_framework\decorators.py", line 50, in handler
    return func(*args, **kwargs)
  File "C:\Users\Robin\craftorgaproj\craftorgaproj\crog\views.py", line 61, in task_items
    task_items = task.task_item.objects.all() #Alle Task_item-Objekte, die dem entsprechenden Task zugeordnet sind
AttributeError: 'Task' object has no attribute 'task_item'
[16/Aug/2020 19:39:37] "GET /tasks/2/items/ HTTP/1.1" 500 93078
Das verstehe ich nicht. Ich habe die Ausführung in der Docu zu ForeignKey so verstanden, dass man das zu dem Objekt bindet, welches n halten soll (hier: Task) und das name-Attribute dafür sorge tragen soll, dass man nicht nur task_item.task sondern auch die andere Richtung task.task_item aufrufen kann...

Hier noch zum Abschluss meiner Frage die entsprechende Methode der Func-View:

Code: Alles auswählen

api_view(['GET','POST'])
def task_items(request, pk):
	try:
		task = Task.objects.get(pk=pk)
	except Task.DoesNotExist:
		return Response(status=status.HTTP_404_NOT_FOUND)

	
	if request.method == 'GET':
		
		#should just take the objects from the specific task!
		task_items = task.task_item.objects.all() #Alle Task_item-Objekte, die dem entsprechenden Task zugeordnet sind
		
		
		item_serializer = Task_itemSerializer(task_items, many=True)
		return Response(item_serializer.data)
	
	elif request.method == 'POST':
		item_serializer = Task_itemSerializer(data=request.data)
		if item_serializer.is_valid():
			item_serializer.save()
			return Response(item_serializer.data, status=status.HTTP_201_CREATED)
		return Response(item_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Vielen Dank schon Mal :))
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

die Verbindung ist schon da, aber nicht "automagisch". Was du suchst ist IMHO select_related.

Gruß, noisefloor
Benutzeravatar
sparrow
User
Beiträge: 4548
Registriert: Freitag 17. April 2009, 10:28

Soweit ich weiß, funktioniert das durchaus automatisch. Allerdings ist er Name dann "{name}_set". Oder man gibt ihn mit "related_name" explizit an.

Also:

Code: Alles auswählen

from django.db import models

# Create your models here.
class Task(models.Model):
	name = models.CharField(max_length=250, null=True)
	created = models.DateTimeField(auto_now_add=True, null=True)

	class Meta:
		ordering = ('name', )

	def __str__(self):
		formatedDate = self.created.strftime("%Y-%m-%d %H:%M:%S")
		return self.name + " " + formatedDate

class Task_item(models.Model):
	name = models.CharField(max_length=250, null=True)
	cost = models.IntegerField(default=0, null=True)
	price = models.IntegerField(default=10, null=True)
	task = models.ForeignKey(Task, on_delete=models.CASCADE, null=True, related_name='task_item')

	class Meta:
		ordering = ('name', )
	
	def __str__(self):
		return self.name 

In der Dokumentation steht das hier.
Sirius3
User
Beiträge: 18299
Registriert: Sonntag 21. Oktober 2012, 17:20

Zusätzlich: eingerückt wird immer mit vier Leerzeichen pro Ebene, keine Tabs. Die korrekte Schreibweise für Klassen ist TaskItem. Variablennamen schreibt man komplett klein: formated_date.
Man benutz Stringformatierung statt +

Code: Alles auswählen

class Task(models.Model):
    name = models.CharField(max_length=250, null=True)
    created = models.DateTimeField(auto_now_add=True, null=True)

    class Meta:
        ordering = ('name', )

    def __str__(self):
        return f"{self.name} {self.created:%Y-%m-%d %H:%M:%S}"
Ich vermute weder name noch created sollten nullable sein
robin_
User
Beiträge: 48
Registriert: Montag 3. August 2020, 17:59

Danke euch für die Antworten hab es jetzt hinbekommen.

Allerdings musste ich den TaskItemSerializer (habs umbenannt) nun anpassen, damit das ganze läuft. Kann mir nicht erklären, wo genau jetzt der Unterschied ist:

Code: Alles auswählen

class TaskItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = TaskItem
        # fiels = ('id', 'name', 'cost', 'price','task')
        fields = '__all__'

Und falls noch jemand es brauchen kann:
Mit dieser Relation bekommt man dann beim Zugriff einen ReverseManager zurück, auf den ähnlich wie den BaseManager zugegriffen werden kann.

Das hat paar Minuten gebraucht :D

Code: Alles auswählen

#richtig:
task_items = task.taskitem.all()

#falsch:
task_items = task.taskitem.objects.all()
Danke euch!
Antworten