網站:https://seniorartlab.pythonanywhere.com/
1.seniorartlab/gallery/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # gallery/models.py from django.db import models class Artwork(models.Model): title = models.CharField(max_length=100) description = models.TextField(blank=True) # 圖片改成可選,這樣可以做純影片作品 image = models.ImageField(upload_to='artworks/', blank=True, null=True) # 新增 mp4 影片欄位 video = models.FileField(upload_to='videos/', blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title |
2.seniorartlab/gallery/admin.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # gallery/admin.py from django.contrib import admin from .models import Artwork @admin.register(Artwork) class ArtworkAdmin(admin.ModelAdmin): list_display = ('title', 'created_at', 'has_image', 'has_video') def has_image(self, obj): return bool(obj.image) has_image.short_description = '有圖片' def has_video(self, obj): return bool(obj.video) has_video.short_description = '有影片' |
3.seniorartlab/gallery/views.py
1 2 3 4 5 6 7 | # gallery/views.py from django.shortcuts import render from .models import Artwork def home(request): artworks = Artwork.objects.all().order_by('-created_at') return render(request, 'gallery/home.html', {'artworks': artworks}) |
4.seniorartlab/gallery/urls.py
1 2 3 4 5 6 7 | # gallery/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.home, name='home'), ] |
5.seniorartlab/seniorartlab/urls.py
1 2 3 4 5 6 7 8 9 10 11 12 13 | # seniorartlab/urls.py from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('gallery.urls')), # 前台首頁與作品 ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) |
6.seniorartlab/seniorartlab/settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """ Django settings for seniorartlab project. Generated by 'django-admin startproject' using Django 5.1.3. For more information on this file, see https://docs.djangoproject.com/en/5.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.1/ref/settings/ """ from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "django-insecure-&6icjhr7uh0dw!5i^5*w0=vn1*4%va_lr14@12b3spl%i=t=vn" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['seniorartlab.pythonanywhere.com'] # Application definition INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", 'gallery', ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "seniorartlab.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / 'templates'], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "seniorartlab.wsgi.application" # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # default static files settings for PythonAnywhere. # see https://help.pythonanywhere.com/pages/DjangoStaticFiles for more info MEDIA_ROOT = '/home/seniorartlab/seniorartlab/media' MEDIA_URL = '/media/' STATIC_ROOT = '/home/seniorartlab/seniorartlab/static' STATIC_URL = '/static/' # 媒體檔案設定 MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' STATIC_URL = '/static/' |
7.seniorartlab/templates/gallery/home.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | <!-- gallery/templates/gallery/home.html --> <!DOCTYPE html> <html lang="zh-Hant"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SeniorArtLab 作品展示</title> <style> * { box-sizing: border-box; } body { margin: 0; font-family: "Microsoft JhengHei", sans-serif; background: #f5f7fb; color: #333; } .page-container { max-width: 1200px; margin: 0 auto; padding: 40px 20px; } .page-title { text-align: center; font-size: 32px; margin-bottom: 10px; color: #222; } .page-subtitle { text-align: center; color: #666; margin-bottom: 36px; font-size: 16px; } .artwork-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px; align-items: start; } .artwork-card { background: #fff; border-radius: 18px; overflow: hidden; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); transition: transform 0.2s ease, box-shadow 0.2s ease; } .artwork-card:hover { transform: translateY(-4px); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12); } .artwork-media { width: 100%; height: auto; display: block; background: #e9ecef; } .artwork-content { padding: 18px; } .artwork-title { font-size: 20px; margin: 0 0 10px; color: #1f2937; line-height: 1.4; } .artwork-description { font-size: 14px; line-height: 1.7; color: #555; margin-bottom: 14px; white-space: pre-line; } .artwork-meta { font-size: 12px; color: #888; border-top: 1px solid #eee; padding-top: 10px; } .empty-box { text-align: center; background: #fff; padding: 50px 20px; border-radius: 16px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06); color: #777; } @media (max-width: 576px) { .page-title { font-size: 26px; } .artwork-grid { grid-template-columns: 1fr; } } </style> </head> <body> <div class="page-container"> <h1 class="page-title">SeniorArtLab 作品展示</h1> <p class="page-subtitle">圖片與影片作品展示牆</p> {% if artworks %} <div class="artwork-grid"> {% for artwork in artworks %} <div class="artwork-card"> {% if artwork.video %} <video class="artwork-media" controls preload="metadata"> <source src="{{ artwork.video.url }}" type="video/mp4"> 您的瀏覽器不支援影片播放。 </video> {% elif artwork.image %} <img src="{{ artwork.image.url }}" alt="{{ artwork.title }}" class="artwork-media"> {% endif %} <div class="artwork-content"> <h2 class="artwork-title">{{ artwork.title }}</h2> <div class="artwork-description"> {% if artwork.description %} {{ artwork.description }} {% else %} 暫無作品說明 {% endif %} </div> <div class="artwork-meta"> 上傳時間:{{ artwork.created_at|date:"Y-m-d H:i" }} </div> </div> </div> {% endfor %} </div> {% else %} <div class="empty-box"> <p>目前沒有作品</p> </div> {% endif %} </div> </body> </html> |