繁体   English   中英

Django以一种形式创建父模型和子模型

[英]Django create parent model and child model in one form

我正在学习 Python 和 Django,并且我正在尝试使用表单创建一个配方。 问题是我有两个模型, RecipeRecipeIngredient ,我不知道如何将配方成分添加到表单中,因为它需要配方的主键,据我所知,在表单之后才创建密钥保存了吗? 那么,当Recipe尚未初始化时,如何使用RecipeRecipeIngredient创建一个配方?

模型.py:

class Recipe(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='image/', blank=True, null=True)
    name = models.CharField(max_length=220) # Lasanga
    description = models.TextField(blank=True, null=True)
    notes = models.TextField(blank=True, null=True)
    cookTime = models.CharField(max_length=50, blank=True, null=True)
    timeStamp = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)

    @property
    def title(self):
        return self.name

    def get_absolute_url(self):
        return reverse("recipes:detail", kwargs={"id": self.id}) # recipes is from url.py app_name
    
    def get_hx_url(self):
        return reverse("recipes:hx-detail", kwargs={"id": self.id}) # recipes is from url.py app_name
    
    def get_edit_url(self):
        return reverse("recipes:update", kwargs={"id": self.id})
    
    def get_image_upload_url(self):
        return reverse("recipes:recipe-ingredient-image-upload", kwargs={"parent_id": self.id})

    def get_delete_url(self):
        return reverse("recipes:delete", kwargs={"id": self.id})
    
    def get_ingredients_children(self):
        return self.recipeingredient_set.all()
    
    def get_instruction_children(self):
        return self.recipeinstruction_set.all()


class RecipeIngredient(models.Model):
    recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
    name = models.CharField(max_length=220) # grilled chicken pasta
    description = models.TextField(blank=True, null=True)
    quantity = models.CharField(max_length=50, blank=True, null=True)
    unit = models.CharField(max_length=50, validators=[validate_unit_of_measure], blank=True, null=True) 
    instructions = models.TextField(blank=True, null=True)
    timeStamp = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)

    

    def get_absolute_url(self):
        return self.recipe.get_absolute_url() # recipe cannot be none

    def get_hx_edit_url(self):
        kwargs = {
            "parent_id": self.recipe.id,
            "id": self.id
        }
        return reverse("recipes:hx-ingredient-detail", kwargs=kwargs)

    def get_delete_url(self):
        kwargs = {
            "parent_id": self.recipe.id,
            "id": self.id
        }
        return reverse("recipes:ingredient-delete", kwargs=kwargs)

视图.py

@login_required
def recipe_create_view(request):
    form = RecipeForm(request.POST or None)
    context = {
        "form": form,
        }
    if form.is_valid():
        obj = form.save(commit=False)
        obj.user = request.user
        obj.save()
        if request.htmx: # necessary to pass headers from htmx response if we want the django to recognise the htmx change
            headers = {
                "HX-Redirect": obj.get_absolute_url()
            }
            return HttpResponse("Created", headers=headers)
        # if request.htmx: # could use this but the url doesn't update, stays as create, would need to use HX-Push header & HttpResponse + context somehow
        #     context = {
        #         "object": obj
        #     }
        #    return render(request, "recipes/partials/detail.html", context)
        return redirect(obj.get_absolute_url())
    return render(request, "recipes/create.html", context)

@login_required
def recipe_ingredient_update_hx_view(request, parent_id=None, id=None): # this is both create & edit, can create
    if not request.htmx:
        raise Http404
    try:
        parent_obj = Recipe.objects.get(id=parent_id, user=request.user)
    
    except:
        parent_obj = None
    
    if parent_obj is None:
        return HttpResponse("Not Found.")

    instance = None
    if id is not None:
        try:
            instance = RecipeIngredient.objects.get(recipe=parent_obj, id=id) # think of this as an object if that helps
    
        except:
            instance = None
    form = RecipeIngredientForm(request.POST or None, instance=instance)
    url = reverse("recipes:hx-ingredient-create", kwargs={"parent_id": parent_obj.id})
    if instance:
        url = instance.get_hx_edit_url()

    context = {
        "url": url,
        "form": form,
        "object": instance
    }
    
    if form.is_valid():
        new_obj =  form.save(commit=False)
        if instance is None:
            new_obj.recipe = parent_obj
        new_obj.save()
        context['object'] = new_obj # because it's possible the object/instance in None
        return render(request, "recipes/partials/ingredient-inline.html", context)

    return render(request, "recipes/partials/ingredient-form.html", context)

表格.py

from django import forms

from .models import Recipe, RecipeIngredient
class RecipeForm(forms.ModelForm):

    class Meta:
        model = Recipe
        fields = ['name', 'image', 'description', 'notes', 'cookTime']
        
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        for field in self.fields:
            new_data = {
            "placeholder": f"Recipe {str(field)}",
            "class": "form-control",
            
            }
            self.fields[str(field)].widget.attrs.update(new_data)

class RecipeIngredientForm(forms.ModelForm):
    class Meta:
        model = RecipeIngredient
        fields = ['name', 'quantity', 'unit']

网址.py

from django.urls import path

from .views import (
    recipe_list_view,
    
    recipe_delete_view,
   
    recipe_create_view,
    recipe_update_view,
    recipe_detail_hx_view,
    recipe_ingredient_update_hx_view,
    recipe_ingredient_delete_view,
    recipe_ingredient_image_upload_view,
    recipe_ingredient_url_scrape_view
    
)

app_name='recipes' # allows use of recipes:list as a reverse url call

urlpatterns = [
    path('', recipe_list_view, name='list'), # index / home / root
    path('create/>', recipe_create_view, name='create'),
    path('hx/<int:parent_id>/ingredient/<int:id>', recipe_ingredient_update_hx_view, name='hx-ingredient-detail'), #or detail
    path('hx/<int:parent_id>/ingredient/', recipe_ingredient_update_hx_view, name='hx-ingredient-create'),
]

创建.html

{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}

<div class="container-fluid px-5">

    <h1 class="pb-5">Create Recipe</h1>

    <div id="recipe-container">
        <form action='.' method="POST" hx-post='.'>
            {% csrf_token %}
            <div class='row'>
                <div class="row d-flex pb-5">
                    <div class="col-12 col-lg-6 justify-content-center d-flex order-first order-lg-last pictureBox"
                        style="height: 400px; width:450; border: solid tomato 1px;">
                        <div class="align-self-center">
                            {{ form.image|as_crispy_field }}
                        </div>
                    </div>
                    <div class="col-12 col-lg-6 order-lg-first">

                        <div class="pb-3">{{ form.name|as_crispy_field }}</div>
                        <div class="pb-3">{{ form.description|as_crispy_field }}</div>
                    </div>

                </div>

                <div class="col-12 col-md-6">
                    {{ form.notes|as_crispy_field }}
                </div>

               
                <div class="col-12 col-md-6">
                    {{ form.cookTime |as_crispy_field }}
                </div>
                
            </div>
    </div>
    <div class='col-12 col-md-4'>

         <!-- ADD INGREDIENTS ? -->

    </div>


    <div class="htmx-indicator">Loading...</div>
    <div class="d-flex">
    <button class="btn btn-success htmx-inverted-indicator" style='margin-top:10px;' type='submit'>Save</button>
    <a class="btn btn-danger" href='{% url "recipes:list" %}'>Delete</a>
    </div>
    {% if message %}
    <p>{{ message }}</p>
    {% endif %}
    </form>
    
</div>
</div>

{% endblock content %}

配料表.html

<form action='.' method="POST" hx-post='{% if url %} {{ url }} {% else %} . {{% endif %}' hx-swap='outerHTML'>
    {% csrf_token %}
    
   {{ form.as_p}}

    <div class="htmx-indicator">Loading...</div>
    <button class="htmx-inverted-indicator" style='margin-top:10px;' type='submit' >Save</button>
</form>

成分内联.html

<div class="py-1" id="ingredient-{{object.id}}">
    <p>{% if not edit %} <b>{{ object.quantity }} {% if object.unit %} {{ object.unit }} {% endif %}</b> {% else %} {{ object.quantity }} {{ object.unit }} {% endif %} - {{ object.name }}</p>
    {% if edit %}
    <button class="btn btn-primary" hx-trigger='click' hx-get='{{ object.get_hx_edit_url }}' hx-target="#ingredient-{{object.id}}">Edit</button> <!-- target will replace whole div-->
    <button class="btn btn-danger" href='{{ object.get_delete_url }}' hx-post='{{ object.get_delete_url }}' hx-confirm="Are you sure you want to delete {{ object.name }}?" hx-trigger='click' hx-target="#ingredient-{{object.id}}" hx-swap="outerHTML">Delete</button>
    {% endif %}
</div>

这个问题的关键是使用表单集,因为您可能希望将多种成分保存到配方中。Django 文档概述了如何使用它们。 您的视图最终会看起来像下面这样,允许您保存父模型,这将为您提供父实例/主键,然后保存成分。

def recipe_create_view(request):
    form = RecipeForm(request.POST or None)
    RecipeIngredientFormset = formset_factory(RecipeIngredientForm)
    formset = RecipeIngredientFormset(request.POST or None)
    
    context = {
        "form": form,
        "formset": formset,
    }
    if request.method == "POST":
        
        if form.is_valid() and formset.is_valid():
            parent = form.save(commit=False)
            parent.user = request.user
            parent.save()
            
            #recipe ingredients
            for form in formset:
                child = form.save(commit=False)
                
                child.recipe = parent
                child.save()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM