ã«ã¹ã¿ã ã¢ã¯ã·ã§ã³ã§Djangoã®ç®¡çã€ã³ã¿ãŒãã§ãŒã¹ããã¹ã¿ãŒããŸããããã°ããŒãã«ãªã¢ããªã±ãŒã·ã§ã³åãã«ã匷åãªäžæ¬æäœãããŒã¿ãšã¯ã¹ããŒããçµ±åãå®è£ ããæ¹æ³ãåŠã³ãŸãã
Django Adminã®åãè§£ãæŸã€ïŒã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã®è§£èª¬
Django管çã€ã³ã¿ãŒãã§ãŒã¹ã¯ããã®ãã¬ãŒã ã¯ãŒã¯ã®æãé åçãªæ©èœã®äžã€ãšããŠé »ç¹ã«æãããããæ¬åœã«çŽ æŽãããããŒã«ã§ãã远å ã®ç®¡çããã«çšããã¯ãšã³ãã³ãŒããäžåæžããã«ãã¢ããªã±ãŒã·ã§ã³ã®ããŒã¿ã管çããããã®å ç¢ã§ããŠãŒã¶ãŒãã¬ã³ããªãŒã§ãå®å šãªæ¹æ³ãããã«æäŸããŸããå€ãã®ãããžã§ã¯ãã«ãšã£ãŠãããã¯ååãããã»ã©ã§ããããããã¢ããªã±ãŒã·ã§ã³ãè€éåããèŠæš¡ãæ¡å€§ããã«ã€ããŠãåçŽãªCRUDïŒäœæãèªã¿åããæŽæ°ãåé€ïŒã¿ã¹ã¯ãè¶ ãããããå°éçã§åŒ·åãªãã³ã³ããã¹ãåºæã®æäœãå¿ èŠã«ãªããŸãã
ããã§ç»å Žããã®ãDjangoã®ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã§ãã管çã¢ã¯ã·ã§ã³ã䜿çšãããšã倿Žãªã¹ãããŒãžããçŽæ¥ãéžæããããªããžã§ã¯ãã®ã»ããã«å¯ŸããŠå®è¡ã§ããç¹å®ã®æäœãéçºè ãå®çŸ©ã§ããŸããæ°çŸã®ãŠãŒã¶ãŒã¢ã«ãŠã³ãããéã¢ã¯ãã£ãããšããŠããŒã¯ããããéžæããæ³šæã®ã«ã¹ã¿ãã€ãºãããã¬ããŒããçæããããè£œåæŽæ°ã®ããããå€éšã®eã³ããŒã¹ãã©ãããã©ãŒã ãšåæãããããç¶æ³ãæ³åããŠã¿ãŠãã ãããããããã¹ãŠããã銎æã¿ã®Django管çç»é¢å ã§æ°ã¯ãªãã¯ã§å®è¡ã§ããŸãããã®ã¬ã€ãã§ã¯ãã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ãçè§£ããå®è£ ãããã¹ã¿ãŒããããã®å æ¬çãªéã®ãããæ¡å ããããããã°ããŒãã«ã¢ããªã±ãŒã·ã§ã³ã®ç®¡çèœåãå€§å¹ ã«æ¡åŒµããåãäžããŸãã
Django Adminã®æ žãšãªã匷ã¿ãçè§£ãã
ã«ã¹ã¿ãã€ãºã«æ·±ãå ¥ãåã«ãDjango Adminã®åºç€ãšãªãåãè©äŸ¡ããããšãéèŠã§ããããã¯åãªãåºæ¬çãªããã¯ãšã³ãã§ã¯ãªããæ¬¡ã®ãããªåçã§ã¢ãã«é§ååã®ã€ã³ã¿ãŒãã§ãŒã¹ã§ãã
- ãã©ãŒã ã®èªåçæ: ã¢ãã«ã«åºã¥ããŠãããŒã¿ã®è¿œå ããã³ç·šéçšã®ãã©ãŒã ãäœæããŸãã
- ãªã¬ãŒã·ã§ã³ã·ããã®åŠç: çŽæçãªãŠã£ãžã§ããã§å€éšããŒãå€å¯Ÿå€ãäžå¯Ÿäžã®ãªã¬ãŒã·ã§ã³ã·ããã管çããŸãã
- èªèšŒãšæ¿èªã®æäŸ: Djangoã®å ç¢ãªãŠãŒã¶ãŒããã³ããŒããã·ã§ã³ã·ã¹ãã ãšã·ãŒã ã¬ã¹ã«çµ±åããŸãã
- ãã£ã«ã¿ãªã³ã°ãšæ€çŽ¢ã®æäŸ: 管çè ãç¹å®ã®ããŒã¿ãšã³ããªãçŽ æ©ãèŠã€ããããšãå¯èœã«ããŸãã
- åœéåã®ãµããŒã: ã€ã³ã¿ãŒãã§ãŒã¹ã®çµã¿èŸŒã¿ç¿»è𳿩èœã«ãããã°ããŒãã«å±éã«å¯Ÿå¿ããŠããŸãã
ãã®ããã«äœ¿ããæ©èœã¯ãéçºæéãåçã«ççž®ããããŒã¿ã®äžè²«ããå®å šãªç®¡çããŒã¿ã«ãä¿èšŒããŸããã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã¯ããã®åŒ·åãªåºç€ã®äžã«æ§ç¯ãããããžãã¹ããžãã¯åºæã®æäœã远å ããããã®ããã¯ãæäŸããŸãã
ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ãäžå¯æ¬ ã§ããçç±
ããã©ã«ãã®ç®¡çã€ã³ã¿ãŒãã§ãŒã¹ã¯åå¥ã®ãªããžã§ã¯ã管çã«ã¯åªããŠããŸãããè€æ°ã®ãªããžã§ã¯ããé¢ããæäœãè€éãªããžãã¹ããžãã¯ãå¿ èŠãšããæäœã«ã¯äžååãªå ŽåããããŸãã以äžã«ãã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ãäžå¯æ¬ ãšãªãããã€ãã®èª¬åŸåã®ããã·ããªãªã瀺ããŸãã
-
äžæ¬ããŒã¿æäœ: æ°åã®ã³ãŒã¹ã管çããeã©ãŒãã³ã°ãã©ãããã©ãŒã ãæ³åããŠã¿ãŠãã ãããæ¬¡ã®ãããªæäœãå¿
èŠã«ãªããããããŸããã
- è€æ°ã®ã³ãŒã¹ããå ¬éæžã¿ããŸãã¯ãäžæžãããšããŠããŒã¯ããã
- éžæããã³ãŒã¹ã®ã°ã«ãŒãã«æ°ããè¬åž«ãå²ãåœãŠãã
- å€ããªã£ãåŠçç»é²ã®ããããåé€ããã
-
ããŒã¿åæãšçµ±å: ã¢ããªã±ãŒã·ã§ã³ã¯ãã°ãã°å€éšã·ã¹ãã ãšé£æºããŸãã管çã¢ã¯ã·ã§ã³ã¯æ¬¡ã®ãããªããšã容æã«ããŸãã
- éžæããè£œåæŽæ°ãå€éšAPIïŒäŸïŒåšåº«ã·ã¹ãã ãæ±ºæžã²ãŒããŠã§ã€ãã°ããŒãã«eã³ããŒã¹ãã©ãããã©ãŒã ïŒã«ããã·ã¥ããã
- æ€çŽ¢ãšã³ãžã³ã®éžæããã³ã³ãã³ãã®ããŒã¿åã€ã³ããã¯ã¹ãããªã¬ãŒããã
- å€éšããžã¹ãã£ã¯ã¹ã·ã¹ãã ã§æ³šæããåºè·æžã¿ããšããŠããŒã¯ããã
-
ã«ã¹ã¿ã ã¬ããŒããšãšã¯ã¹ããŒã: Django adminã¯åºæ¬çãªãšã¯ã¹ããŒããæäŸããŸãããéåžžã«å
·äœçãªã¬ããŒããå¿
èŠã«ãªãå ŽåããããŸãã
- ããŒã±ãã£ã³ã°ãã£ã³ããŒã³ã®ããã«ãéžæãããŠãŒã¶ãŒã®ã¡ãŒã«ã¢ãã¬ã¹ã®CSVãã¡ã€ã«ãçæããã
- ç¹å®ã®æéã®è«æ±æžã®PDFãµããªãŒãäœæããã
- äŒèšã·ã¹ãã ãšã®çµ±åã®ããã«è²¡åããŒã¿ããšã¯ã¹ããŒãããã
-
ã¯ãŒã¯ãããŒç®¡ç: è€éãªã¯ãŒã¯ãããŒãæã€ã¢ããªã±ãŒã·ã§ã³ã®å Žåãã¢ã¯ã·ã§ã³ã¯ããã»ã¹ãå¹çåã§ããŸãã
- è€æ°ã®ä¿çäžã®ãŠãŒã¶ãŒç»é²ãæ¿èªãŸãã¯æåŠããã
- éžæãããµããŒããã±ãããã解決æžã¿ãã®ç¶æ ã«ç§»åããã
- ãŠãŒã¶ãŒã®ã°ã«ãŒãã«ã¡ãŒã«éç¥ãããªã¬ãŒããã
-
èªåã¿ã¹ã¯ããªã¬ãŒ: æã«ã¯ã管çã¢ã¯ã·ã§ã³ãããé·ãããã»ã¹ãéå§ããã ãã®ããšããããŸãã
- ç¹å®ã®ããŒã¿ã»ããã®æ¯æ¥ã®ããŒã¿ããã¯ã¢ãããéå§ããã
- éžæãããšã³ããªã«å¯ŸããŠããŒã¿ç§»è¡ã¹ã¯ãªãããå®è¡ããã
ãããã®ã·ããªãªã¯ãã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ãåçŽãªç®¡çã¿ã¹ã¯ãšè€éã§ããžãã¹äžéèŠãªæäœãšã®éã®ã®ã£ãããã©ã®ããã«åãããã瀺ããDjango Adminãçã«å æ¬çãªç®¡çããŒã¿ã«ã«ããŠããŸãã
åºæ¬çãªã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã®æ§æ
ãã®æ žãšãªãã®ã¯ãDjango管çã¢ã¯ã·ã§ã³ã¯ãModelAdmin
ã¯ã©ã¹å
ã®Python颿°ãŸãã¯ã¡ãœããã§ããããã¯ãmodeladmin
ãrequest
ãããã³queryset
ãšãã3ã€ã®åŒæ°ãåããŸãã
modeladmin
: ããã¯çŸåšã®ModelAdmin
ã€ã³ã¹ã¿ã³ã¹ã§ãã管ç察象ã¢ãã«ã«é¢é£ããæ§ã ãªãŠãŒãã£ãªãã£ã¡ãœããã屿§ãžã®ã¢ã¯ã»ã¹ãæäŸããŸããrequest
: çŸåšã®HTTPãªã¯ãšã¹ããªããžã§ã¯ãã§ããããã¯æšæºçãªDjangoHttpRequest
ãªããžã§ã¯ãã§ããããŠãŒã¶ãŒæ å ±ãPOST/GETããŒã¿ãã»ãã·ã§ã³ããŒã¿ãªã©ã«ã¢ã¯ã»ã¹ã§ããŸããqueryset
: çŸåšéžæãããŠãããªããžã§ã¯ãã®QuerySet
ã§ããããã¯ã¢ã¯ã·ã§ã³ãåäœãã¹ããã¹ãŠã®ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãå«ããããéèŠãªéšåã§ãã
ã¢ã¯ã·ã§ã³é¢æ°ã¯ãã¹ã ãŒãºãªãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã確ä¿ããããã«ãå
ã®å€æŽãªã¹ãããŒãžãžã®HttpResponseRedirect
ãè¿ãããšãçæ³çã§ããäœãè¿ããªãå ŽåïŒãŸãã¯None
ãè¿ãå ŽåïŒã管çç»é¢ã¯åã«çŸåšã®ããŒãžãåèªã¿èŸŒã¿ããŸããDjangoã®ã¡ãã»ãŒãžãã¬ãŒã ã¯ãŒã¯ã䜿çšããŠãŠãŒã¶ãŒãã£ãŒãããã¯ãæäŸããããšãè¯ãç¿æ
£ã§ãã
ã¹ããããã€ã¹ãããïŒåããŠã®ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã®å®è£
å®çšçãªäŸãäœæããŸããããProduct
ã¢ãã«ããããéžæãã補åããå²åŒæžã¿ããšããŠããŒã¯ããã¢ã¯ã·ã§ã³ãå¿
èŠã ãšããŸãã
# myapp/models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
is_discounted = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
次ã«ãmyapp/admin.py
ã«ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã远å ããŸãããã
# myapp/admin.py
from django.contrib import admin, messages
from django.db.models import QuerySet
from django.http import HttpRequest
from .models import Product
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'price', 'is_discounted', 'created_at')
list_filter = ('is_discounted', 'created_at')
search_fields = ('name',)
# Define the custom admin action function
def make_discounted(self, request: HttpRequest, queryset: QuerySet):
updated_count = queryset.update(is_discounted=True)
self.message_user(
request,
f"{updated_count} product(s) were successfully marked as discounted.",
messages.SUCCESS
)
make_discounted.short_description = "Mark selected products as discounted"
# Register the action with the ModelAdmin
actions = [make_discounted]
説æ:
- ã¢ã¯ã·ã§ã³é¢æ°:
make_discounted
ãProductAdmin
å ã®ã¡ãœãããšããŠå®çŸ©ããŸãããããã¯ãåäžã®ModelAdmin
ã«åºæã®ã¢ã¯ã·ã§ã³ã«æšå¥šãããã¢ãããŒãã§ãã - ã·ã°ããã£:
self
ïŒã¡ãœããã§ããããïŒãrequest
ãããã³queryset
ãæ£ããåãå ¥ããŸãã - ããžãã¯: 颿°å
ã§ã
queryset.update(is_discounted=True)
ã䜿çšããŠãéžæããããã¹ãŠã®ãªããžã§ã¯ããåäžã®ããŒã¿ããŒã¹ã¯ãšãªã§å¹ççã«æŽæ°ããŸããããã¯ãã¯ãšãªã»ãããå埩åŠçããŠåãªããžã§ã¯ããåå¥ã«ä¿åãããããã¯ããã«ããã©ãŒãã³ã¹ãé«ãã§ãã - ãŠãŒã¶ãŒãã£ãŒãããã¯:
self.message_user()
ã¯ã管çã€ã³ã¿ãŒãã§ãŒã¹ã§ãŠãŒã¶ãŒã«ã¡ãã»ãŒãžã衚瀺ããããã«ModelAdmin
ã«ãã£ãŠæäŸããã䟿å©ãªã¡ãœããã§ããè¯å®çãªè¡šç€ºã®ããã«messages.SUCCESS
ã䜿çšããŠããŸãã short_description
: ãã®å±æ§ã¯ã管çç»é¢ã®ãã¢ã¯ã·ã§ã³ãããããããŠã³ãªã¹ãã«è¡šç€ºããããŠãŒã¶ãŒãã¬ã³ããªãŒãªååãå®çŸ©ããŸããããããªããšã颿°ã®çã®åç§°ïŒäŸïŒãmake_discountedãïŒã衚瀺ããããŠãŒã¶ãŒã«ãšã£ãŠçæ³çã§ã¯ãããŸãããactions
ãªã¹ã: æåŸã«ãProductAdmin
ã¯ã©ã¹ã®actions
ãªã¹ãã«ãã®é¢æ°åç §ã远å ããããšã§ãã¢ã¯ã·ã§ã³ãç»é²ããŸãã
ããã§ãDjango Adminã®Product倿Žãªã¹ãããŒãžã«ç§»åããããã€ãã®è£œåãéžæããããããããŠã³ãããéžæãã補åãå²åŒæžã¿ãšããŠããŒã¯ããéžæãããšãéžæããã¢ã€ãã ãæŽæ°ãããæåã¡ãã»ãŒãžã衚瀺ãããŸãã
ãŠãŒã¶ãŒç¢ºèªã«ããã¢ã¯ã·ã§ã³ã®åŒ·åïŒå¶çºçãªæäœã®é²æ¢
ããã¹ãŠéžæãåé€ããããã¹ãŠã®ã³ã³ãã³ããå ¬éãã®ãããªã¢ã¯ã·ã§ã³ãã確èªãªãã«çŽæ¥å®è¡ãããšãé倧ãªããŒã¿æå€±ãæå³ããªãçµæã«ã€ãªããå¯èœæ§ããããŸããæ©å¯æ§ã®é«ãæäœã«ã¯ãäžéçãªç¢ºèªã¹ãããã远å ããããšãäžå¯æ¬ ã§ããããã¯éåžžã確èªãã©ãŒã ãå«ãã«ã¹ã¿ã ãã³ãã¬ãŒããã¬ã³ããªã³ã°ããããšãå«ã¿ãŸãã
make_discounted
ã¢ã¯ã·ã§ã³ãæ¹åããŠã確èªã¹ããããå«ããŸãããã説æã®ããã«ãããå°ãäžè¬çãªã確èªä»ãã§ã¢ã€ãã ããæ¿èªæžã¿ããšããŠããŒã¯ãããããã«å€æŽããŠã¿ãŸãããã
# myapp/models.py (assuming a Post model)
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
status = models.CharField(max_length=20, default='draft', choices=[
('draft', 'Draft'),
('pending', 'Pending Review'),
('approved', 'Approved'),
('rejected', 'Rejected'),
])
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
ãŸãã確èªçšã®ã·ã³ãã«ãªãã©ãŒã ãå¿ èŠã§ãã
# myapp/forms.py
from django import forms
class ConfirmationForm(forms.Form):
confirm = forms.BooleanField(
label="Are you sure you want to perform this action?",
required=True,
widget=forms.HiddenInput # We'll handle the display in the template
)
_selected_action = forms.CharField(widget=forms.HiddenInput)
action = forms.CharField(widget=forms.HiddenInput)
次ã«ãmyapp/admin.py
å
ã®ã¢ã¯ã·ã§ã³ã§ãã
# myapp/admin.py
from django.contrib import admin, messages
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from .models import Post
from .forms import ConfirmationForm
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'status', 'created_at')
list_filter = ('status',)
search_fields = ('title',)
def mark_posts_approved(self, request: HttpRequest, queryset: QuerySet) -> HttpResponseRedirect | None:
# Check if the user confirmed the action
if 'apply' in request.POST:
form = ConfirmationForm(request.POST)
if form.is_valid():
updated_count = queryset.update(status='approved')
self.message_user(
request,
f"{updated_count} post(s) were successfully marked as approved.",
messages.SUCCESS
)
return HttpResponseRedirect(request.get_full_path())
# If not confirmed, or GET request, show confirmation page
else:
# Store the selected objects' primary keys in a hidden field
# This is crucial for passing the selection across the confirmation page
context = self.admin_site.each_context(request)
context['queryset'] = queryset
context['form'] = ConfirmationForm(initial={
'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME),
'action': 'mark_posts_approved',
})
context['action_name'] = self.mark_posts_approved.short_description
context['title'] = _("Confirm Action")
# Render a custom confirmation template
return render(request, 'admin/confirmation_action.html', context)
mark_posts_approved.short_description = _("Mark selected posts as approved")
actions = [mark_posts_approved]
ãããŠå¯Ÿå¿ãããã³ãã¬ãŒã (templates/admin/confirmation_action.html
):
{# templates/admin/confirmation_action.html #}
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}
{% block extrastyle %}{{ block.super }}
{% endblock %}
{% block content %}
{% endblock %}
ãã³ãã¬ãŒããæ€åºå¯èœã§ããããšã確èªããã«ã¯ãã¢ããªå
ã«templates
ãã£ã¬ã¯ããªïŒmyapp/templates/admin/
ïŒãããããsettings.py
ã®TEMPLATES
èšå®ã§æ§æãããŠããããšã確èªããŠãã ããã
確èªã¢ã¯ã·ã§ã³ã®äž»èŠèŠçŽ :
- æ¡ä»¶ä»ãããžãã¯: ã¢ã¯ã·ã§ã³ã¯
if 'apply' in request.POST:
ããã§ãã¯ããŸãããŠãŒã¶ãŒã確èªãã©ãŒã ãéä¿¡ããå Žåãã¢ã¯ã·ã§ã³ã¯ç¶è¡ãããŸãããã以å€ã®å Žåã¯ã確èªããŒãžãã¬ã³ããªã³ã°ããŸãã _selected_action
: ãã®é衚瀺ãã£ãŒã«ãã¯éåžžã«éèŠã§ããDjango管çç»é¢ã¯ãéžæããããªããžã§ã¯ãã®ãã©ã€ããªããŒãaction_checkbox
ãšããPOSTãã©ã¡ãŒã¿ãä»ããŠéä¿¡ããŸãã確èªãã©ãŒã ãã¬ã³ããªã³ã°ãããšããrequest.POST.getlist(admin.ACTION_CHECKBOX_NAME)
ã䜿çšããŠãããã®IDãæœåºããããããé衚瀺ã®å ¥åãšããŠç¢ºèªãã©ãŒã ã«å床枡ããŸããããã«ããããŠãŒã¶ãŒã確èªãããšãã«å ã®éžæãã¢ã¯ã·ã§ã³ã«åéä¿¡ãããããšãä¿èšŒããŸãã- ã«ã¹ã¿ã ãã©ãŒã : ãŠãŒã¶ãŒã®ç¢ºèªããã£ããã£ããããã«ãã·ã³ãã«ãª
forms.Form
ã䜿çšãããŸããconfirm
ã«ã¯éè¡šç€ºå ¥åã䜿çšããŸããããã³ãã¬ãŒãã¯è³ªåãçŽæ¥è¡šç€ºããŸãã - ãã³ãã¬ãŒãã®ã¬ã³ããªã³ã°: ã«ã¹ã¿ã ã®
confirmation_action.html
ã衚瀺ããããã«django.shortcuts.render()
ã䜿çšããŸãã衚瀺ã®ããã«queryset
ãšform
ããã³ãã¬ãŒãã«æž¡ããŸãã - CSRFä¿è·: ã¯ãã¹ãµã€ããªã¯ãšã¹ããã©ãŒãžã§ãªæ»æãé²ãããã«ãåžžã«ãã©ãŒã ã«
{% csrf_token %}
ãå«ããŠãã ããã - æ»ãå€: æ£åžžãªå®è¡åŸããŠãŒã¶ãŒã管ç倿Žãªã¹ãããŒãžã«æ»ãããã«
HttpResponseRedirect(request.get_full_path())
ãè¿ããŸããããã«ãããæŽæ°æã«äºéãã©ãŒã éä¿¡ãé²ããŸãã
ãã®ãã¿ãŒã³ã¯ãéèŠãªç®¡çã¢ã¯ã·ã§ã³ã®ç¢ºèªãã€ã¢ãã°ãå®è£ ããããã®å ç¢ãªæ¹æ³ãæäŸãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãåäžãããè²»çšã®ãããééããé²ããŸãã
ãŠãŒã¶ãŒå ¥åãã¢ã¯ã·ã§ã³ã«è¿œå ããïŒåçãªæäœ
æã«ã¯ãåçŽãªãã¯ã/ããããã®ç¢ºèªã ãã§ã¯ååã§ã¯ãããŸããã管çè ã«ã¯ãã¢ã¯ã·ã§ã³ã®çç±ããã£ãŒã«ãã®æ°ããå€ãäºåå®çŸ©ããããªã¹ãããã®éžæãªã©ã远å ã®å ¥åãå¿ èŠã«ãªãå ŽåããããŸããããã«ã¯ãããè€éãªãã©ãŒã ã管çã¢ã¯ã·ã§ã³ã«çµã¿èŸŒãå¿ èŠããããŸãã
äŸãšããŠãéžæããPost
ãªããžã§ã¯ãã«å¯ŸããŠãã¹ããŒã¿ã¹ã倿Žããã³ã¡ã³ãã远å ãããã¢ã¯ã·ã§ã³ãèããŠã¿ãŸãããã
# myapp/forms.py
from django import forms
from .models import Post
class ChangePostStatusForm(forms.Form):
_selected_action = forms.CharField(widget=forms.HiddenInput)
action = forms.CharField(widget=forms.HiddenInput)
new_status = forms.ChoiceField(
label="New Status",
choices=Post.STATUS_CHOICES, # Assuming STATUS_CHOICES defined in Post model
required=True
)
comment = forms.CharField(
label="Reason/Comment (optional)",
required=False,
widget=forms.Textarea(attrs={'rows': 3})
)
# Add STATUS_CHOICES to Post model
# myapp/models.py
from django.db import models
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('pending', 'Pending Review'),
('approved', 'Approved'),
('rejected', 'Rejected'),
]
title = models.CharField(max_length=255)
content = models.TextField()
status = models.CharField(max_length=20, default='draft', choices=STATUS_CHOICES)
comment_history = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
次ã«ãmyapp/admin.py
å
ã®ã¢ã¯ã·ã§ã³ã§ãã
# myapp/admin.py (continued)
from django.contrib import admin, messages
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from .models import Post
from .forms import ChangePostStatusForm # Import the new form
# ... (ProductAdmin and PostAdmin definitions, other imports)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'status', 'created_at')
list_filter = ('status',)
search_fields = ('title',)
# Existing mark_posts_approved action...
def change_post_status_with_comment(self, request: HttpRequest, queryset: QuerySet) -> HttpResponseRedirect | None:
form = None
if 'apply' in request.POST:
form = ChangePostStatusForm(request.POST)
if form.is_valid():
new_status = form.cleaned_data['new_status']
comment = form.cleaned_data['comment']
updated_count = 0
for post in queryset:
post.status = new_status
if comment:
post.comment_history = (post.comment_history or '') + f"\n[{request.user.username}] changed to {new_status} with comment: {comment}"
post.save()
updated_count += 1
self.message_user(
request,
f"{updated_count} post(s) had their status changed to '{new_status}' and comment added.",
messages.SUCCESS
)
return HttpResponseRedirect(request.get_full_path())
# If not confirmed, or GET request, show the input form
if not form:
form = ChangePostStatusForm(initial={
'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME),
'action': 'change_post_status_with_comment',
})
context = self.admin_site.each_context(request)
context['queryset'] = queryset
context['form'] = form
context['action_name'] = self.change_post_status_with_comment.short_description
context['title'] = _("Change Post Status and Add Comment")
return render(request, 'admin/change_status_action.html', context)
change_post_status_with_comment.short_description = _("Change status for selected posts (with comment)")
actions = [
mark_posts_approved,
change_post_status_with_comment
]
ãããŠå¯Ÿå¿ãããã³ãã¬ãŒã (templates/admin/change_status_action.html
):
{# templates/admin/change_status_action.html #}
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}
{% block extrastyle %}{{ block.super }}
{% endblock %}
{% block content %}
{% endblock %}
ãŠãŒã¶ãŒå ¥åã¢ã¯ã·ã§ã³ã®äž»èŠãªãã€ã³ã:
- å°çšãã©ãŒã : å¿
èŠãªãã¹ãŠã®ãŠãŒã¶ãŒå
¥åããã£ããã£ããããã«ãå°çšã®
forms.Form
ïŒãŸãã¯åäžã®ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãšå¯Ÿè©±ããå Žåã¯forms.ModelForm
ïŒãäœæããŸãã - ãã©ãŒã ããªããŒã·ã§ã³: Djangoã®ãã©ãŒã ããªããŒã·ã§ã³ã¯ãããŒã¿ã®æŽåæ§ãšãšã©ãŒã¡ãã»ãŒãžãèªåçã«åŠçããŸãã
form.cleaned_data
ã«ã¢ã¯ã»ã¹ããåã«if form.is_valid():
ããã§ãã¯ããŠãã ããã - ã€ãã¬ãŒã·ã§ã³ãšäžæ¬æŽæ°ã®æ¯èŒ:
comment_history
ã«ã³ã¡ã³ãã远å ããããã«ãã¯ãšãªã»ãããå埩åŠçããåãªããžã§ã¯ããåå¥ã«ä¿åããŠããç¹ã«æ³šç®ããŠãã ãããããã¯ã.update()
ãåãªããžã§ã¯ãã«å¯ŸããŠæ¢åã®ãã£ãŒã«ãã«ããã¹ãã远å ãããããªè€éãªããžãã¯ãé©çšã§ããªãããã§ããéåžžã«å€§èŠæš¡ãªã¯ãšãªã»ããã§ã¯ããã©ãŒãã³ã¹ãäœäžããŸããããªããžã§ã¯ãããšã®ããžãã¯ãå¿ èŠãªæäœã«ã¯äžå¯æ¬ ã§ããåçŽãªãã£ãŒã«ãæŽæ°ã«ã¯ãqueryset.update()
ãæšå¥šãããŸãã - ãšã©ãŒãå«ããã©ãŒã ã®åã¬ã³ããªã³ã°:
form.is_valid()
ãFalse
ãè¿ãå Žåãrender()
颿°ã¯ãã©ãŒã ãåã³è¡šç€ºããããªããŒã·ã§ã³ãšã©ãŒãèªåçã«å«ããŸããããã¯æšæºçãªDjangoã®ãã©ãŒã åŠçãã¿ãŒã³ã§ãã
ãã®ã¢ãããŒãã«ããã管çè ãã¢ã¯ã·ã§ã³ã«ç¹å®ã®ãã©ã¡ãŒã¿ãæäŸã§ãããéåžžã«æè»ã§åçãªç®¡çæäœãå¯èœã«ãªããŸãã
é«åºŠãªã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ïŒåºç€ãè¶ ããŠ
ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã®çã®åã¯ãå€éšãµãŒãã¹ãšã®çµ±åãè€éãªã¬ããŒãã®çæããŸãã¯é·æéå®è¡ãããã¿ã¹ã¯ã®å®è¡æã«çºæ®ãããŸããããã€ãã®é«åºŠãªãŠãŒã¹ã±ãŒã¹ãèŠãŠãããŸãããã
1. ããŒã¿åæã®ããã®å€éšAPIã®åŒã³åºã
Djangoã¢ããªã±ãŒã·ã§ã³ã補åã«ã¿ãã°ã管çããŠãããéžæãã補åãå€éšã®Eã³ããŒã¹ãã©ãããã©ãŒã ãã°ããŒãã«ãªåšåº«ç®¡çã·ã¹ãã ïŒIMSïŒãšAPIçµç±ã§åæããå¿ èŠããããšæ³åããŠã¿ãŠãã ããã管çã¢ã¯ã·ã§ã³ã¯ããã®åæãããªã¬ãŒã§ããŸãã
以åã«å®çŸ©ããProduct
ã¢ãã«ããããéžæãã補åã®æŽæ°ãå€éšã®åšåº«ãµãŒãã¹ã«ããã·ã¥ããããšä»®å®ããŸãã
# myapp/admin.py (continued)
import requests # You'll need to install requests: pip install requests
# ... other imports ...
# Assuming ProductAdmin from earlier
class ProductAdmin(admin.ModelAdmin):
# ... existing list_display, list_filter, search_fields ...
def sync_products_to_external_ims(self, request: HttpRequest, queryset: QuerySet) -> HttpResponseRedirect | None:
# Check for confirmation (similar to previous examples if needed)
if 'apply' in request.POST:
# Simulate an external API endpoint
EXTERNAL_IMS_API_URL = "https://api.example.com/v1/products/sync/"
API_KEY = "your_secret_api_key" # In a real app, use settings.py or environment variables
successful_syncs = 0
failed_syncs = []
for product in queryset:
data = {
"product_id": product.id,
"name": product.name,
"price": str(product.price), # Convert Decimal to string for JSON
"is_discounted": product.is_discounted,
# Add other relevant product data
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.post(EXTERNAL_IMS_API_URL, json=data, headers=headers, timeout=5) # 5-second timeout
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
successful_syncs += 1
except requests.exceptions.RequestException as e:
failed_syncs.append(f"Product {product.name} (ID: {product.id}): {e}")
except Exception as e:
failed_syncs.append(f"Product {product.name} (ID: {product.id}): Unexpected error: {e}")
if successful_syncs > 0:
self.message_user(
request,
f"{successful_syncs} product(s) successfully synchronized with external IMS.",
messages.SUCCESS
)
if failed_syncs:
error_message = f"Failed to synchronize {len(failed_syncs)} product(s):\n" + "\n".join(failed_syncs)
self.message_user(request, error_message, messages.ERROR)
return HttpResponseRedirect(request.get_full_path())
# Initial GET request or non-apply POST request: show confirmation (if desired)
context = self.admin_site.each_context(request)
context['queryset'] = queryset
context['form'] = ConfirmationForm(initial={
'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME),
'action': 'sync_products_to_external_ims',
})
context['action_name'] = self.sync_products_to_external_ims.short_description
context['title'] = _("Confirm Data Synchronization")
return render(request, 'admin/confirmation_action.html', context) # Re-use confirmation template
sync_products_to_external_ims.short_description = _("Synchronize selected products with external IMS")
actions = [
# ... other actions ...
sync_products_to_external_ims,
]
APIçµ±åã«é¢ããéèŠãªèæ ®äºé :
- ãšã©ãŒåŠç: ãããã¯ãŒã¯ãªã¯ãšã¹ãã«ã¯å
ç¢ãª
try-except
ãããã¯ãäžå¯æ¬ ã§ããæ¥ç¶ãšã©ãŒãã¿ã€ã ã¢ãŠããããã³APIåºæã®ãšã©ãŒïŒäŸïŒ401 Unauthorizedã404 Not Foundã500 Internal Server ErrorïŒãåŠçããŸãã - ã»ãã¥ãªãã£: APIããŒãæ©å¯æ§ã®é«ãè³æ Œæ
å ±ã¯ãããŒãã³ãŒããã¹ãã§ã¯ãããŸãããDjangoèšå®ïŒäŸïŒ
settings.EXTERNAL_API_KEY
ïŒãŸãã¯ç°å¢å€æ°ã䜿çšããŠãã ããã - ããã©ãŒãã³ã¹: å€ãã®ã¢ã€ãã ãåæããå ŽåãAPIãªã¯ãšã¹ãããããåŠçããããããã«è¯ãã®ã¯éåæã¿ã¹ã¯ã䜿çšããããšãæ€èšããŠãã ããïŒäžèšåç §ïŒã
- ãŠãŒã¶ãŒãã£ãŒãããã¯: ã©ã®ã¢ã€ãã ãæåããã©ã®ã¢ã€ãã ã倱æãããã«ã€ããŠããšã©ãŒã®è©³çްãšãšãã«æç¢ºãªã¡ãã»ãŒãžãæäŸããŸãã
2. ã¬ããŒããšããŒã¿ãšã¯ã¹ããŒãã®çæ (CSV/Excel)
éžæããããŒã¿ããšã¯ã¹ããŒãããããšã¯ãéåžžã«äžè¬çãªèŠä»¶ã§ããDjango管çã¢ã¯ã·ã§ã³ã¯ãéžæããã¯ãšãªã»ããããçŽæ¥ã«ã¹ã¿ã CSVãŸãã¯Excelãã¡ã€ã«ãçæããããã«äœ¿çšã§ããŸãã
éžæããPost
ããŒã¿ãCSVãã¡ã€ã«ã«ãšã¯ã¹ããŒãããã¢ã¯ã·ã§ã³ãäœæããŸãããã
# myapp/admin.py (continued)
import csv
from django.http import HttpResponse
# ... other imports ...
class PostAdmin(admin.ModelAdmin):
# ... existing list_display, list_filter, search_fields, actions ...
def export_posts_as_csv(self, request: HttpRequest, queryset: QuerySet) -> HttpResponse:
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="posts_export.csv"'
writer = csv.writer(response)
# Write header row
writer.writerow(['Title', 'Status', 'Created At', 'Content Preview'])
for post in queryset:
writer.writerow([
post.title,
post.get_status_display(), # Use get_FOO_display() for choice fields
post.created_at.strftime("%Y-%m-%d %H:%M:%S"),
post.content[:100] + '...' if len(post.content) > 100 else post.content # Truncate long content
])
self.message_user(
request,
f"{queryset.count()} post(s) successfully exported to CSV.",
messages.SUCCESS
)
return response
export_posts_as_csv.short_description = _("Export selected posts as CSV")
actions = [
# ... other actions ...
export_posts_as_csv,
]
Excelãšã¯ã¹ããŒãã®å Žå: éåžžãopenpyxl
ãpandas
ã®ãããªã©ã€ãã©ãªã䜿çšããŸããååã¯åæ§ã§ããã¡ã€ã«ãã¡ã¢ãªå
ã§çæããæ£ããContent-Type
ïŒäŸïŒ.xlsxã®å Žåã¯application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
ïŒãšãšãã«HttpResponse
ã«æ·»ä»ããŸãã
3. é·æéå®è¡ãããã¿ã¹ã¯ã®éåæã¢ã¯ã·ã§ã³
管çã¢ã¯ã·ã§ã³ãããªãã®æéãèŠããæäœïŒäŸïŒå€§èŠæš¡ãªããŒã¿ã»ããã®åŠçãè€éãªã¬ããŒãã®çæãé ãå€éšAPIãšã®ããåãïŒãå«ãå Žåãããããåæçã«å®è¡ãããšWebãµãŒããŒããããã¯ãããã¿ã€ã ã¢ãŠãã壿ªãªãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã«ã€ãªãããŸãã解決çã¯ãCeleryã®ãããªã¿ã¹ã¯ãã¥ãŒã·ã¹ãã ã䜿çšããŠããããã®ã¿ã¹ã¯ãããã¯ã°ã©ãŠã³ãã¯ãŒã«ãŒã«ãªãããŒãããããšã§ãã
åææ¡ä»¶:
- Celery: CeleryãšãããŒã«ãŒïŒäŸïŒRedisãŸãã¯RabbitMQïŒãã€ã³ã¹ããŒã«ããŸãã
- Django-Celery-Results: ãªãã·ã§ã³ã§ãããã¿ã¹ã¯ã®çµæãããŒã¿ããŒã¹ã«ä¿åããã®ã«äŸ¿å©ã§ãã
APIåæã®äŸãéåæã«é©å¿ãããŸãããã
# myproject/celery.py (standard Celery setup)
import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
app = Celery('myproject')
# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')
# myapp/tasks.py
import requests
from celery import shared_task
from django.contrib.auth import get_user_model
from django.apps import apps
@shared_task
def sync_product_to_external_ims_task(product_id, admin_user_id):
Product = apps.get_model('myapp', 'Product')
User = get_user_model()
try:
product = Product.objects.get(pk=product_id)
admin_user = User.objects.get(pk=admin_user_id)
EXTERNAL_IMS_API_URL = "https://api.example.com/v1/products/sync/"
API_KEY = "your_secret_api_key" # Use environment variables or Django settings
data = {
"product_id": product.id,
"name": product.name,
"price": str(product.price),
"is_discounted": product.is_discounted,
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
response = requests.post(EXTERNAL_IMS_API_URL, json=data, headers=headers, timeout=10)
response.raise_for_status()
# Log success (e.g., to Django logs or a specific model for tracking)
print(f"Product {product.name} (ID: {product.id}) synchronized by {admin_user.username} successfully.")
except Product.DoesNotExist:
print(f"Product with ID {product_id} not found.")
except User.DoesNotExist:
print(f"Admin user with ID {admin_user_id} not found.")
except requests.exceptions.RequestException as e:
print(f"API sync failed for product {product_id}: {e}")
except Exception as e:
print(f"Unexpected error during sync for product {product_id}: {e}")
# myapp/admin.py (continued)
from django.contrib import admin, messages
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from .models import Product # Assuming Product model from earlier
from .tasks import sync_product_to_external_ims_task # Import your Celery task
class ProductAdmin(admin.ModelAdmin):
# ... existing list_display, list_filter, search_fields ...
def async_sync_products_to_external_ims(self, request: HttpRequest, queryset: QuerySet) -> HttpResponseRedirect | None:
if 'apply' in request.POST:
admin_user_id = request.user.id
for product in queryset:
# Enqueue the task for each selected product
sync_product_to_external_ims_task.delay(product.id, admin_user_id)
self.message_user(
request,
f"{queryset.count()} product(s) synchronization tasks have been queued.",
messages.SUCCESS
)
return HttpResponseRedirect(request.get_full_path())
# Initial GET request or non-apply POST request: show confirmation
context = self.admin_site.each_context(request)
context['queryset'] = queryset
context['form'] = ConfirmationForm(initial={
'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME),
'action': 'async_sync_products_to_external_ims',
})
context['action_name'] = self.async_sync_products_to_external_ims.short_description
context['title'] = _("Confirm Asynchronous Data Synchronization")
return render(request, 'admin/confirmation_action.html', context) # Re-use confirmation template
async_sync_products_to_external_ims.short_description = _("Queue async sync for selected products to IMS")
actions = [
# ... other actions ...
async_sync_products_to_external_ims,
]
åäœã®ä»çµã¿:
- 管çã¢ã¯ã·ã§ã³ã¯ãéãåŠçãçŽæ¥å®è¡ãã代ããã«ãéžæãããã¯ãšãªã»ãããå埩åŠçããŸãã
- éžæãããåãªããžã§ã¯ãã«ã€ããŠãCeleryã¿ã¹ã¯ã§
.delay()
ãåŒã³åºããé¢é£ãããã©ã¡ãŒã¿ïŒäŸïŒãã©ã€ããªããŒããŠãŒã¶ãŒIDïŒãæž¡ããŸããããã«ãããã¿ã¹ã¯ããã¥ãŒã«è¿œå ãããŸãã - 管çã¢ã¯ã·ã§ã³ã¯çŽã¡ã«
HttpResponseRedirect
ãšæåã¡ãã»ãŒãžãè¿ããã¿ã¹ã¯ããã¥ãŒã«è¿œå ãããããšããŠãŒã¶ãŒã«éç¥ããŸããWebãªã¯ãšã¹ãã¯çæéã§å®äºããŸãã - ããã¯ã°ã©ãŠã³ãã§ã¯ãCeleryã¯ãŒã«ãŒããããŒã«ãŒãããããã®ã¿ã¹ã¯ãåãåããWebãªã¯ãšã¹ããšã¯ç¬ç«ããŠå®è¡ããŸãã
ããæŽç·Žãããã·ããªãªã§ã¯ã管çç»é¢å
ã§ã¿ã¹ã¯ã®é²è¡ç¶æ³ãšçµæã远跡ãããå ŽåããããŸããdjango-celery-results
ã®ãããªã©ã€ãã©ãªã¯ãã¿ã¹ã¯ã®ç¶æ
ãããŒã¿ããŒã¹ã«ä¿åã§ãããããã¹ããŒã¿ã¹ããŒãžãžã®ãªã³ã¯ã衚瀺ãããã管çUIãåçã«æŽæ°ãããã§ããŸãã
ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã®ãã¹ããã©ã¯ãã£ã¹
ã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ãå ç¢ã§ãå®å šã§ãä¿å®å¯èœã§ããããšãä¿èšŒããããã«ã以äžã®ãã¹ããã©ã¯ãã£ã¹ã«åŸã£ãŠãã ããã
1. ããŒããã·ã§ã³ãšæ¿èª
ãã¹ãŠã®ç®¡çè ããã¹ãŠã®ã¢ã¯ã·ã§ã³ã«ã¢ã¯ã»ã¹ã§ããã¹ãã§ã¯ãããŸãããDjangoã®ããŒããã·ã§ã³ã·ã¹ãã ã䜿çšããŠã誰ãã¢ã¯ã·ã§ã³ã衚瀺ããå®è¡ã§ããããå¶åŸ¡ã§ããŸãã
æ¹æ³1: has_perm()
ã®äœ¿çš
ã¢ã¯ã·ã§ã³é¢æ°å ã§ç¹å®ã®ããŒããã·ã§ã³ããã§ãã¯ã§ããŸãã
def sensitive_action(self, request, queryset):
if not request.user.has_perm('myapp.can_perform_sensitive_action'):
self.message_user(request, _("You do not have permission to perform this action."), messages.ERROR)
return HttpResponseRedirect(request.get_full_path())
# ... sensitive action logic ...
次ã«ãmyapp/models.py
ã®Meta
ã¯ã©ã¹å
ã§ã«ã¹ã¿ã ããŒããã·ã§ã³ãå®çŸ©ããŸãã
# myapp/models.py
class Product(models.Model):
# ... fields ...
class Meta:
permissions = [
("can_perform_sensitive_action", "Can perform sensitive product action"),
]
makemigrations
ãšmigrate
ãå®è¡ãããšããã®ããŒããã·ã§ã³ã¯Django Adminã®ãŠãŒã¶ãŒãšã°ã«ãŒãã«è¡šç€ºãããŸãã
æ¹æ³2: get_actions()
ã«ããã¢ã¯ã·ã§ã³ã®åçãªå¶é
ModelAdmin
ã®get_actions()
ã¡ãœããããªãŒããŒã©ã€ãããŠãçŸåšã®ãŠãŒã¶ãŒã®ããŒããã·ã§ã³ã«åºã¥ããŠã¢ã¯ã·ã§ã³ãæ¡ä»¶ä»ãã§åé€ã§ããŸãã
# myapp/admin.py
class ProductAdmin(admin.ModelAdmin):
# ... actions definition ...
def get_actions(self, request: HttpRequest):
actions = super().get_actions(request)
# Remove the 'make_discounted' action if the user doesn't have a specific permission
if not request.user.has_perm('myapp.change_product'): # Or a custom permission like 'can_discount_product'
if 'make_discounted' in actions:
del actions['make_discounted']
return actions
ãã®ã¢ãããŒãã«ãããæš©éã®ãªããŠãŒã¶ãŒã«ã¯ã¢ã¯ã·ã§ã³ãå®å šã«è¡šç€ºãããªããªããããã¯ãªãŒã³ãªUIãæäŸãããŸãã
2. å ç¢ãªãšã©ãŒåŠç
é害ãäºæž¬ããé©åã«åŠçããŠãã ãããããŒã¿ããŒã¹æäœãå€éšAPIåŒã³åºãããã¡ã€ã«æäœã®åšãã«ã¯try-except
ãããã¯ã䜿çšããŠãã ãããself.message_user(request, ..., messages.ERROR)
ã䜿çšããŠããŠãŒã¶ãŒã«æçãªãšã©ãŒã¡ãã»ãŒãžãæäŸããŠãã ããã
3. ãŠãŒã¶ãŒãã£ãŒãããã¯ãšã¡ãã»ãŒãž
åžžã«ã¢ã¯ã·ã§ã³ã®çµæããŠãŒã¶ãŒã«éç¥ããŠãã ãããDjangoã®ã¡ãã»ãŒãžãã¬ãŒã ã¯ãŒã¯ãããã«çæ³çã§ãã
messages.SUCCESS
: æåããæäœã®å Žåãmessages.WARNING
: éšåçãªæåãŸãã¯è»œåŸ®ãªåé¡ã®å Žåãmessages.ERROR
: é倧ãªå€±æã®å Žåãmessages.INFO
: äžè¬çãªæ å ±ã¡ãã»ãŒãžã®å ŽåïŒäŸïŒãã¿ã¹ã¯ã¯æ£åžžã«ãã¥ãŒã«è¿œå ãããŸããããïŒã
4. ããã©ãŒãã³ã¹ã«é¢ããèæ ®äºé
- äžæ¬æäœ: å¯èœãªéããäžæ¬ããŒã¿ããŒã¹æäœã«ã¯
queryset.update()
ãŸãã¯queryset.delete()
ã䜿çšããŠãã ããããããã¯åäžã®SQLã¯ãšãªãå®è¡ããåã ã®ãªããžã§ã¯ããå埩åŠçããŠä¿å/åé€ãããããã¯ããã«å¹ççã§ãã - ã¢ãããã¯ãã©ã³ã¶ã¯ã·ã§ã³: è€æ°ã®ããŒã¿ããŒã¹å€æŽã1ã€ã®åäœãšããŠæåãŸãã¯å€±æããªããã°ãªããªãã¢ã¯ã·ã§ã³ã®å Žåã
from django.db import transaction
ãšwith transaction.atomic():
ã䜿çšããŠããžãã¯ããã©ã³ã¶ã¯ã·ã§ã³ã§ã©ããããŠãã ããã - éåæã¿ã¹ã¯: é·æéå®è¡ãããæäœïŒAPIåŒã³åºããéãèšç®ããã¡ã€ã«åŠçïŒã®å ŽåãWebãµãŒããŒã®ãããã¯ãé²ãããã«ãããããããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãã¥ãŒïŒäŸïŒCeleryïŒã«ãªãããŒãããŠãã ããã
5. åå©çšæ§ãšçµç¹å
è€æ°ã®ModelAdmin
ã¯ã©ã¹ãç°ãªããããžã§ã¯ãã§åœ¹ç«ã€å¯èœæ§ã®ããã¢ã¯ã·ã§ã³ãããå Žåã¯ãããããã«ãã»ã«åããããšãæ€èšããŠãã ããã
- ã¹ã¿ã³ãã¢ãã³é¢æ°: ã¢ã¯ã·ã§ã³ã
myapp/admin_actions.py
ãã¡ã€ã«å ã§ã¹ã¿ã³ãã¢ãã³é¢æ°ãšããŠå®çŸ©ããããããModelAdmin
ã¯ã©ã¹ã«ã€ã³ããŒãããŸãã - Mixin: é¢é£ãããã©ãŒã ããã³ãã¬ãŒããæã€ããè€éãªã¢ã¯ã·ã§ã³ã®å Žåã¯ã
ModelAdmin
ããã·ã³ã¯ã©ã¹ãäœæããŸãã
# myapp/admin_actions.py
from django.contrib import messages
from django.http import HttpRequest, HttpResponseRedirect
from django.db.models import QuerySet
def mark_as_active(modeladmin, request: HttpRequest, queryset: QuerySet):
updated = queryset.update(is_active=True)
modeladmin.message_user(request, f"{updated} item(s) marked as active.", messages.SUCCESS)
mark_as_active.short_description = "Mark selected as active"
# myapp/admin.py
from django.contrib import admin
from .models import MyModel
from .admin_actions import mark_as_active
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'is_active')
actions = [mark_as_active]
6. 管çã¢ã¯ã·ã§ã³ã®ãã¹ã
管çã¢ã¯ã·ã§ã³ã¯ããžãã¹ããžãã¯ã®éèŠãªéšåã§ããã培åºçã«ãã¹ãããå¿
èŠããããŸãããã¥ãŒããã¹ãããã«ã¯Djangoã®Client
ããç¹å®ã®ç®¡çæ©èœã«ã¯admin.ModelAdmin
ãã¹ãã¯ã©ã€ã¢ã³ãã䜿çšããŸãã
# myapp/tests.py
from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.contrib import admin
from .models import Product
from .admin import ProductAdmin # Import your ModelAdmin
User = get_user_model()
class ProductAdminActionTests(TestCase):
def setUp(self):
self.admin_user = User.objects.create_superuser('admin', 'admin@example.com', 'password')
self.client = Client()
self.client.login(username='admin', password='password')
self.p1 = Product.objects.create(name="Product A", price=10.00, is_discounted=False)
self.p2 = Product.objects.create(name="Product B", price=20.00, is_discounted=False)
self.p3 = Product.objects.create(name="Product C", price=30.00, is_discounted=True)
self.admin_site = admin.AdminSite()
self.model_admin = ProductAdmin(Product, self.admin_site)
def test_make_discounted_action(self):
# Simulate selecting products and performing the action
change_list_url = reverse('admin:myapp_product_changelist')
response = self.client.post(change_list_url, {
admin.ACTION_CHECKBOX_NAME: [self.p1.pk, self.p2.pk],
'action': 'make_discounted',
'index': 0, # Required for some Django admin internal logic
}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '2 product(s) were successfully marked as discounted.')
self.p1.refresh_from_db()
self.p2.refresh_from_db()
self.p3.refresh_from_db()
self.assertTrue(self.p1.is_discounted)
self.assertTrue(self.p2.is_discounted)
self.assertTrue(self.p3.is_discounted) # This one was already discounted
def test_make_discounted_action_confirmation(self):
# For actions with confirmation, you'd test the two-step process
change_list_url = reverse('admin:myapp_post_changelist') # Assuming Post model for confirmation example
post1 = Post.objects.create(title='Test Post 1', content='...', status='draft')
post2 = Post.objects.create(title='Test Post 2', content='...', status='draft')
# Step 1: Request confirmation page
response = self.client.post(change_list_url, {
admin.ACTION_CHECKBOX_NAME: [post1.pk, post2.pk],
'action': 'mark_posts_approved',
'index': 0,
})
self.assertEqual(response.status_code, 200)
self.assertIn(b"Confirm Action", response.content) # Check if confirmation page is rendered
# Step 2: Submit confirmation form
response = self.client.post(change_list_url, {
admin.ACTION_CHECKBOX_NAME: [post1.pk, post2.pk],
'action': 'mark_posts_approved',
'apply': 'Yes, I\'m sure',
'confirm': 'on', # Value for a checkbox if rendered as checkbox
'_selected_action': [str(post1.pk), str(post2.pk)], # Must be passed back from form
'index': 0,
}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '2 post(s) were successfully marked as approved.')
post1.refresh_from_db()
post2.refresh_from_db()
self.assertEqual(post1.status, 'approved')
self.assertEqual(post2.status, 'approved')
7. ã»ãã¥ãªãã£ã®ãã¹ããã©ã¯ãã£ã¹
- å ¥åæ€èšŒ: åžžã«ïŒç¢ºèªãã©ãŒã ããã®ãã®ãªã©ïŒãŠãŒã¶ãŒå ¥åãDjangoãã©ãŒã ã䜿çšããŠæ€èšŒããŠãã ãããçã®ãŠãŒã¶ãŒå ¥åãæ±ºããŠä¿¡é Œããªãã§ãã ããã
- CSRFä¿è·: ãã¹ãŠã®ãã©ãŒã ïŒã«ã¹ã¿ã ã¢ã¯ã·ã§ã³ãã³ãã¬ãŒãå
ã®ã«ã¹ã¿ã ãã©ãŒã ãå«ãïŒã«
{% csrf_token %}
ãå«ãŸããŠããããšã確èªããŠãã ããã - SQLã€ã³ãžã§ã¯ã·ã§ã³: Djangoã®ORMã¯ããã©ã«ãã§SQLã€ã³ãžã§ã¯ã·ã§ã³ããä¿è·ããŸãããã ããã¢ã¯ã·ã§ã³å ã§è€éãªã¯ãšãªã®ããã«çã®SQLã«åãæ¿ããå Žåã¯æ³šæããŠãã ããã
- æ©å¯ããŒã¿: æ©å¯ããŒã¿ïŒAPIããŒãå人æ å ±ïŒã¯å®å šã«åŠçããŠãã ãããäžå¿ èŠã«ãã°ã«èšé²ããããšãé¿ããé©åãªã¢ã¯ã»ã¹å¶åŸ¡ã確ä¿ããŠãã ããã
ããããèœãšã穎ãšè§£æ±ºç
çµéšè±å¯ãªéçºè ã§ãã管çã¢ã¯ã·ã§ã³ã§åé¡ã«ééããããšããããŸãã以äžã«ãããããèœãšã穎ãããã€ã瀺ããŸãã
-
return HttpResponseRedirect
ãå¿ãã:èœãšã穎: æ°ããããŒãžãã¬ã³ããªã³ã°ããªãæåããã¢ã¯ã·ã§ã³ïŒãšã¯ã¹ããŒããªã©ïŒã®åŸã«
HttpResponseRedirect
ãè¿ãã®ãå¿ããããšãããŒãžã¯ãªãã¬ãã·ã¥ããããããããŸããããæåã¡ãã»ãŒãžã衚瀺ãããªãã£ããããã©ãŠã¶ã®ãªãã¬ãã·ã¥æã«ã¢ã¯ã·ã§ã³ã2åå®è¡ããããããå¯èœæ§ããããŸãã解決ç: ãã¡ã€ã«ïŒCSVãªã©ïŒãé ä¿¡ããŠããå Žåãå¥ã®ããŒãžãã¬ã³ããªã³ã°ããŠããå Žåãé€ããã¢ã¯ã·ã§ã³ããžãã¯ã®å®äºåŸã«ã¯ãã¢ã¯ã·ã§ã³é¢æ°ãåžžã«
return HttpResponseRedirect(request.get_full_path())
ïŒãŸãã¯ç¹å®ã®URLïŒã§çµäºããŠãã ããã -
確èªãã©ãŒã ã§
POST
ãšGET
ãåŠçããªã:èœãšã穎: ã¢ã¯ã·ã§ã³ãžã®åæãªã¯ãšã¹ããšããã«ç¶ããã©ãŒã éä¿¡ãåããã®ãšããŠæ±ãã確èªãªãã«ã¢ã¯ã·ã§ã³ãå®è¡ããããããã©ãŒã ãæ£ãã衚瀺ãããªãã£ããããããšã«ã€ãªããã
解決ç: åæãªã¯ãšã¹ãïŒãã©ãŒã ã®è¡šç€ºïŒãšç¢ºèªéä¿¡ïŒããŒã¿ã®åŠçïŒãåºå¥ããããã«ãæ¡ä»¶ä»ãããžãã¯ïŒäŸïŒ
if 'apply' in request.POST:
ãŸãã¯request.method == 'POST'
ïŒã䜿çšããŠãã ããã -
å€§èŠæš¡ãªã¯ãšãªã»ããã§ã®ããã©ãŒãã³ã¹åé¡:
èœãšã穎: äœåãã®ãªããžã§ã¯ããå埩åŠçããããããã«
.save()
ãåŒã³åºããããéžæãããåã¢ã€ãã ã«å¯ŸããŠè€éãªèšç®ãåæçã«å®è¡ãããããããšã解決ç: äžæ¬ãã£ãŒã«ã倿Žã«ã¯
queryset.update()
ã䜿çšããŠãã ãããè€éã§é·æéå®è¡ãããããŸãã¯I/OããŠã³ããªã¿ã¹ã¯ã®å ŽåãCeleryã䜿çšããŠéåæåŠçãæ¡çšããŠãã ãããã¢ã¯ã·ã§ã³ãæ¬åœã«å°ããªãµãã»ããã®ã¿ã察象ãšããŠããå Žåã¯ãããŒãžããŒã·ã§ã³ãå¶éãæ€èšããŠãã ããã -
éžæããããªããžã§ã¯ãIDã®èª€ã£ãæž¡ãæ¹:
èœãšã穎: 確èªããŒãžãå®è£ ããéã«ãæåã®POSTãã確èªãã©ãŒã ã«ããããŠæçµçãªPOSTã«ãéžæããããªããžã§ã¯ãã®ãã©ã€ããªããŒãå«ã
_selected_action
éè¡šç€ºå ¥åãæž¡ãã®ãå¿ããããšã解決ç: 確èªãã©ãŒã ãšãã³ãã¬ãŒãã
request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
ãæ£ããåŠçãããããã®IDã確èªãã©ãŒã ã®éè¡šç€ºå ¥åãšããŠååã蟌ã¿ããŠããããšã確èªããŠãã ããã -
ããŒããã·ã§ã³ã®ç«¶åãŸãã¯äžè¶³:
èœãšã穎: 管çè ã«ã¢ã¯ã·ã§ã³ã衚瀺ãããªãããŸãã¯ã¢ã¯ã»ã¹ã§ããã¯ããªã®ã«ããŒããã·ã§ã³æåŠãšã©ãŒã衚瀺ãããã
解決ç:
get_actions()
ã®ãªãŒããŒã©ã€ããšãã¢ã¯ã·ã§ã³å ã®request.user.has_perm()
ãã§ãã¯ãå確èªããŠãã ãããã«ã¹ã¿ã ããŒããã·ã§ã³ãMeta
ã§å®çŸ©ããããã€ã°ã¬ãŒã·ã§ã³ãå®è¡ãããŠããããšã確èªããŠãã ããã管çç»é¢ã§ãŠãŒã¶ãŒ/ã°ã«ãŒãã®å²ãåœãŠãæ€èšŒããŠãã ããã
çµè«ïŒDjango Adminã匷åãã
Django管çã€ã³ã¿ãŒãã§ãŒã¹ã¯ãåãªãããŒã¿ç®¡çããŒã«ä»¥äžã®ãã®ã§ããæŽç·Žããã管çã¯ãŒã¯ãããŒãæ§ç¯ããããã®åŒ·åãªãã¬ãŒã ã¯ãŒã¯ã§ããã«ã¹ã¿ã 管çã¢ã¯ã·ã§ã³ã掻çšããããšã§ãåçŽãªäžæ¬æŽæ°ããå€éšã·ã¹ãã ãšã®è€éãªçµ±åãã«ã¹ã¿ã ã¬ããŒãã®çæãŸã§ãäºå®äžããããããžãã¹èŠä»¶ãæºããããã«ãã®æ©èœãæ¡åŒµã§ããŸãã
ãã®ã¬ã€ãã§ã¯ãå ç¢ã§å®å šã§ããŠãŒã¶ãŒãã¬ã³ããªãŒãªç®¡çã¢ã¯ã·ã§ã³ãäœæããããã®åºæ¬çãªæŠå¿µãå®çšçãªå®è£ ãããã³é«åºŠãªæè¡ã«ã€ããŠèª¬æããŸããããŠãŒã¶ãŒãã£ãŒãããã¯ãåªå ãã匷åãªãšã©ãŒåŠçãå®è£ ããå€§èŠæš¡ãªããŒã¿ã»ããã®ããã©ãŒãã³ã¹ãèæ ®ããåžžã«é©åãªæ¿èªãç¶æããããšãå¿ããªãã§ãã ããããããã®ååã念é ã«çœ®ãã°ãDjango Adminã®å¯èœæ§ãæå€§éã«åŒãåºããã°ããŒãã«ã«ã¢ããªã±ãŒã·ã§ã³ãšããŒã¿ã管çããããã®ãããã«äžå¯æ¬ ãªè³ç£ã«ããããšãã§ããŸãã
仿¥ããã«ã¹ã¿ã ã¢ã¯ã·ã§ã³ã®å®éšãå§ãã管çå¹çãé£èºçã«åäžããã®ãèŠãŠãã ããïŒ