lzx02 6 months ago
parent e7e6e31d72
commit cd6f7cf697

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Django default" uuid="4780d78e-bbdc-4708-aa57-23b7fa11976b">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/videoproject/settings.py</remarks>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/video</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -0,0 +1,14 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="server.myapp.admin.myapp" />
</list>
</option>
</inspection_tool>
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (server)" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/videoproject-master.iml" filepath="$PROJECT_DIR$/.idea/videoproject-master.iml" />
</modules>
</component>
</project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="videoproject/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.8 (server)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

@ -0,0 +1,2 @@
# django

@ -0,0 +1 @@
# 初始化

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(Comment)

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CommentConfig(AppConfig):
name = 'comment'

@ -0,0 +1,28 @@
import datetime
from django.conf import settings
from django.db import models
from video.models import Video
class CommentQuerySet(models.query.QuerySet):
def get_count(self):
return self.count()
def get_today_count(self):
return self.exclude(timestamp__lt=datetime.date.today()).count()
class Comment(models.Model):
list_display = ("content","timestamp",)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
nickname = models.CharField(max_length=30,blank=True, null=True)
avatar = models.CharField(max_length=100,blank=True, null=True)
video = models.ForeignKey(Video, on_delete=models.CASCADE)
content = models.CharField(max_length=100)
timestamp = models.DateTimeField(auto_now_add=True)
objects = CommentQuerySet.as_manager()
class Meta:
db_table = "v_comment"

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = 'comment'
urlpatterns = [
path('submit_comment/<int:pk>',views.submit_comment, name='submit_comment'),
path('get_comments/', views.get_comments, name='get_comments'),
]

@ -0,0 +1,80 @@
from datetime import datetime
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import *
from django.template.loader import render_to_string
from ratelimit.decorators import ratelimit
from video.forms import CommentForm
from video.models import Video
@ratelimit(key='ip', rate='2/m')
def submit_comment(request,pk):
"""
每分钟限制发2条
"""
was_limited = getattr(request, 'limited', False)
if was_limited:
return JsonResponse({"code": 1, 'msg': '评论太频繁了请1分钟后再试'})
pass
video = get_object_or_404(Video, pk = pk)
form = CommentForm(data=request.POST)
if form.is_valid():
# print('success')
new_comment = form.save(commit=False)
new_comment.user = request.user
new_comment.nickname = request.user.nickname
new_comment.avatar = request.user.avatar
new_comment.video = video
new_comment.save()
data = dict()
data['nickname'] = request.user.nickname
data['avatar'] = request.user.avatar
data['timestamp'] = datetime.fromtimestamp(datetime.now().timestamp())
data['content'] = new_comment.content
comments = list()
comments.append(data)
html = render_to_string(
"comment/comment_single.html", {"comments": comments})
return JsonResponse({"code":0,"html": html})
return JsonResponse({"code":1,'msg':'评论失败!'})
def get_comments(request):
if not request.is_ajax():
return HttpResponseBadRequest()
page = request.GET.get('page')
page_size = request.GET.get('page_size')
video_id = request.GET.get('video_id')
video = get_object_or_404(Video, pk=video_id)
comments = video.comment_set.order_by('-timestamp').all()
comment_count = len(comments)
paginator = Paginator(comments, page_size)
try:
rows = paginator.page(page)
except PageNotAnInteger:
rows = paginator.page(1)
except EmptyPage:
rows = []
if len(rows) > 0:
code = 0
html = render_to_string(
"comment/comment_single.html", {"comments": rows})
else:
code = 1
html = ""
return JsonResponse({
"code":code,
"html": html,
"comment_count": comment_count
})

@ -1 +0,0 @@
Subproject commit 53d3ff190ca4690eba21dcec36ff8570b73425b1

File diff suppressed because it is too large Load Diff

@ -0,0 +1,112 @@
import smtplib
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mass_mail, send_mail
from django.http import HttpResponseBadRequest, HttpResponse
from django.shortcuts import *
from django.utils.html import strip_tags
from django.views.generic import View
def get_page_list(paginator, page):
"""
分页逻辑
if 页数>=10:
当前页<=5起始页为1
当前页>(总页数-5)起始页为(总页数-9)
其他情况 起始页为(当前页-5)
"""
page_list = []
if paginator.num_pages > 10:
if page.number <= 5:
start_page = 1
elif page.number > paginator.num_pages - 5:
start_page = paginator.num_pages - 9
else:
start_page = page.number - 5
for i in range(start_page, start_page + 10):
page_list.append(i)
else:
for i in range(1, paginator.num_pages + 1):
page_list.append(i)
return page_list
def ajax_required(f):
"""Not a mixin, but a nice decorator to validate than a request is AJAX"""
def wrap(request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest()
return f(request, *args, **kwargs)
wrap.__doc__ = f.__doc__
wrap.__name__ = f.__name__
return wrap
def send_html_email(subject, html_message, to_list):
plain_message = strip_tags(html_message)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, plain_message, from_email, to_list, html_message=html_message)
def send_email(subject, content, to_list):
"""
Example:
subject = 'test subject'
content = 'hello, this is content'
to_list = ['abc@qq.com','abcd@163.com']
send_email(subject, content, to_list)
"""
try:
message = (subject, content, settings.EMAIL_HOST_USER, to_list)
# do not forget set password
print("--> is sending email")
send_mass_mail((message,))
except smtplib.SMTPException:
print("--> send fail")
return HttpResponse("fail")
else:
print("--> send success")
return HttpResponse("success")
class AuthorRequiredMixin(View):
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj != self.request.user:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class AdminUserRequiredMixin(View):
"""
管理员拦截器
"""
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_staff:
return redirect('myadmin:login')
return super().dispatch(request, *args, **kwargs)
class SuperUserRequiredMixin(View):
"""
超级用户拦截器
"""
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_superuser:
return HttpResponse('无权限')
return super().dispatch(request, *args, **kwargs)

@ -0,0 +1,16 @@
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'videoproject.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

@ -0,0 +1,2 @@
from django.contrib import admin

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MyadminConfig(AppConfig):
name = 'myadmin'

@ -0,0 +1,156 @@
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError
from users.models import User
from video.models import Video, Classification
class UserLoginForm(AuthenticationForm):
username = forms.CharField(min_length=4,max_length=30,
error_messages={
'min_length': '用户名不少于4个字符',
'max_length': '用户名不能多于30个字符',
'required': '用户名不能为空',
},
widget=forms.TextInput(attrs={'placeholder': '请输入用户名'}))
password = forms.CharField(min_length=8,max_length=30,
error_messages={
'min_length': '密码不少于8个字符',
'max_length': '密码不能多于30个字符',
'required': '密码不能为空',
},
widget=forms.PasswordInput(attrs={'placeholder': '请输入密码'}))
class Meta:
model = User
fields = ['username', 'password']
error_messages = {'invalid_login': '用户名或密码错误', }
class VideoPublishForm(forms.ModelForm):
title = forms.CharField(min_length=4, max_length=200, required=True,
error_messages={
'min_length': '至少4个字符',
'max_length': '不能多于200个字符',
'required': '标题不能为空'
},
widget=forms.TextInput(attrs={'placeholder': '请输入内容'}))
desc = forms.CharField(min_length=4, max_length=200, required=True,
error_messages={
'min_length': '至少4个字符',
'max_length': '不能多于200个字符',
'required': '描述不能为空'
},
widget=forms.Textarea(attrs={'placeholder': '请输入内容'}))
cover = forms.ImageField(required=True,
error_messages={
'required': '封面不能为空'
},
widget=forms.FileInput(attrs={'class' : 'n'}))
status = forms.CharField(min_length=1, max_length=1, required=False,
widget=forms.HiddenInput(attrs={'value':'0'}))
class Meta:
model = Video
fields = ['title', 'desc','status', 'cover', 'classification']
class VideoEditForm(forms.ModelForm):
title = forms.CharField(min_length=4, max_length=200, required=True,
error_messages={
'min_length': '至少4个字符',
'max_length': '不能多于200个字符',
'required': '标题不能为空'
},
widget=forms.TextInput(attrs={'placeholder': '请输入内容'}))
desc = forms.CharField(min_length=4, max_length=200, required=True,
error_messages={
'min_length': '至少4个字符',
'max_length': '不能多于200个字符',
'required': '描述不能为空'
},
widget=forms.Textarea(attrs={'placeholder': '请输入内容'}))
cover = forms.ImageField(required=True,
error_messages={
'required': '封面不能为空'
},
widget=forms.FileInput(attrs={'class' : 'n'}))
status = forms.CharField(min_length=1,max_length=1,required=False,
widget=forms.HiddenInput())
# classification = forms.ModelChoiceField(queryset=Classification.objects.all())
# classification = forms.CharField(min_length=1,max_length=1,required=False,
# widget=forms.HiddenInput())
class Meta:
model = Video
fields = ['title', 'desc', 'status', 'cover','classification']
class UserAddForm(forms.ModelForm):
username = forms.CharField(min_length=4,max_length=30,
error_messages={
'min_length': '用户名不少于4个字符',
'max_length': '用户名不能多于30个字符',
'required': '用户名不能为空',
},
widget=forms.TextInput(attrs={'placeholder': '请输入用户名'}))
password = forms.CharField(min_length=8,max_length=30,
error_messages={
'min_length': '密码不少于8个字符',
'max_length': '密码不能多于30个字符',
'required': '密码不能为空',
},
widget=forms.PasswordInput(attrs={'placeholder': '请输入密码'}))
class Meta:
model = User
fields = ['username', 'password','is_staff' ]
def username_validate(value):
if value == "admin":
raise ValidationError('不能编辑超级管理员')
class UserEditForm(forms.ModelForm):
username = forms.CharField(min_length=4, max_length=30, required=True,
validators=[username_validate],
error_messages={
'min_length': '至少4个字符',
'max_length': '不能多于30个字符',
'required': '用户名不能为空'
},
widget=forms.TextInput(attrs={'placeholder': '请输入用户名'}))
class Meta:
model = User
fields = ['username','is_staff']
class ClassificationAddForm(forms.ModelForm):
title = forms.CharField(min_length=2, max_length=30, required=True,
error_messages={
'min_length': '至少2个字符',
'max_length': '不能多于30个字符',
'required': '不能为空'
},
widget=forms.TextInput(attrs={'placeholder': '请输入分类名称'}))
class Meta:
model = Classification
fields = ['title', 'status' ]
class ClassificationEditForm(forms.ModelForm):
title = forms.CharField(min_length=2, max_length=30, required=True,
error_messages={
'min_length': '至少2个字符',
'max_length': '不能多于30个字符',
'required': '不能为空'
},
widget=forms.TextInput(attrs={'placeholder': '请输入分类名称'}))
class Meta:
model = Classification
fields = ['title','status']

@ -0,0 +1,9 @@
from chunked_upload.models import ChunkedUpload
from django.db import models
# Create your models here.
class MyChunkedUpload(ChunkedUpload):
pass
# Override the default ChunkedUpload to make the `user` field nullable
MyChunkedUpload._meta.get_field('user').null = True

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,46 @@
from django.urls import path
from . import views
app_name = 'myadmin'
urlpatterns = [
path('login/', views.login, name='login'),
path('logout/', views.logout, name='logout'),
#----------------------总览---------------------------
path('', views.IndexView.as_view(), name='index'),
#----------------------视频管理------------------------
path('video_list/', views.VideoListView.as_view(), name='video_list'),
path('video_add/', views.AddVideoView.as_view(), name='video_add'),
path('chunked_upload/', views.MyChunkedUploadView.as_view(), name='api_chunked_upload'),
path('chunked_upload_complete/', views.MyChunkedUploadCompleteView.as_view(),name='api_chunked_upload_complete'),
path('video_publish/<int:pk>/', views.VideoPublishView.as_view(), name='video_publish'),
path('video_publish_success/', views.VideoPublishSuccessView.as_view(), name='video_publish_success'),
path('video_edit/<int:pk>/', views.VideoEditView.as_view(), name='video_edit'),
path('video_delete/', views.video_delete, name='video_delete'),
#----------------------分类管理----------------------------
path('classification_add/', views.ClassificationAddView.as_view(), name='classification_add'),
path('classification_list/', views.ClassificationListView.as_view(), name='classification_list'),
path('classification_edit/<int:pk>/', views.ClassificationEditView.as_view(), name='classification_edit'),
path('classification_delete/', views.classification_delete, name='classification_delete'),
#----------------------评论管理----------------------------
path('comment_list/', views.CommentListView.as_view(), name='comment_list'),
path('comment_delete/', views.comment_delete, name='comment_delete'),
#----------------------用户管理-------------------------
path('user_add/', views.UserAddView.as_view(), name='user_add'),
path('user_list/', views.UserListView.as_view(), name='user_list'),
path('user_edit/<int:pk>',views.UserEditView.as_view(), name='user_edit'),
path('user_delete/', views.user_delete, name='user_delete'),
#-----------------------订阅通知-------------------------
path('subscribe/', views.SubscribeView.as_view(), name='subscribe'),
# -----------------------用户反馈-------------------------
path('feedback_list/', views.FeedbackListView.as_view(), name='feedback_list'),
path('feedback_delete/', views.feedback_delete, name='feedback_delete'),
]

@ -0,0 +1,369 @@
import logging
import smtplib
import datetime
from chunked_upload.views import ChunkedUploadView, ChunkedUploadCompleteView
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout
from django.http import JsonResponse
from django.shortcuts import *
from django.template.loader import render_to_string
from django.views import generic
from django.views.decorators.http import require_http_methods
from django.views.generic import TemplateView
from comment.models import Comment
from helpers import get_page_list, AdminUserRequiredMixin, ajax_required, SuperUserRequiredMixin, send_html_email
from users.models import User, Feedback
from video.models import Video, Classification
from .forms import UserLoginForm, VideoPublishForm, VideoEditForm, UserAddForm, UserEditForm, ClassificationAddForm, \
ClassificationEditForm
from .models import MyChunkedUpload
logger = logging.getLogger('my_logger')
def login(request):
if request.method == 'POST':
form = UserLoginForm(request=request, data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(username=username, password=password)
if user is not None and user.is_staff:
auth_login(request, user)
return redirect('myadmin:index')
else:
form.add_error('', '请输入管理员账号')
else:
form = UserLoginForm()
return render(request, 'myadmin/login.html', {'form': form})
def logout(request):
auth_logout(request)
return redirect('myadmin:login')
class IndexView(AdminUserRequiredMixin, generic.View):
"""
总览数据
"""
def get(self, request):
video_count = Video.objects.get_count()
video_has_published_count = Video.objects.get_published_count()
video_not_published_count = Video.objects.get_not_published_count()
user_count = User.objects.count()
user_today_count = User.objects.exclude(date_joined__lt=datetime.date.today()).count()
comment_count = Comment.objects.get_count()
comment_today_count = Comment.objects.get_today_count()
data = {"video_count": video_count,
"video_has_published_count": video_has_published_count,
"video_not_published_count": video_not_published_count,
"user_count": user_count,
"user_today_count": user_today_count,
"comment_count": comment_count,
"comment_today_count": comment_today_count}
return render(self.request, 'myadmin/index.html', data)
class AddVideoView(SuperUserRequiredMixin, TemplateView):
template_name = 'myadmin/video_add.html'
class MyChunkedUploadView(ChunkedUploadView):
model = MyChunkedUpload
field_name = 'the_file'
class MyChunkedUploadCompleteView(ChunkedUploadCompleteView):
model = MyChunkedUpload
def on_completion(self, uploaded_file, request):
print('uploaded--->', uploaded_file.name)
pass
def get_response_data(self, chunked_upload, request):
video = Video.objects.create(file=chunked_upload.file)
return {'code': 0, 'video_id': video.id, 'msg': 'success'}
class VideoPublishView(SuperUserRequiredMixin, generic.UpdateView):
model = Video
form_class = VideoPublishForm
template_name = 'myadmin/video_publish.html'
def get_context_data(self, **kwargs):
context = super(VideoPublishView, self).get_context_data(**kwargs)
clf_list = Classification.objects.all().values()
clf_data = {'clf_list':clf_list}
context.update(clf_data)
return context
def get_success_url(self):
return reverse('myadmin:video_publish_success')
class VideoPublishSuccessView(generic.TemplateView):
template_name = 'myadmin/video_publish_success.html'
class VideoEditView(SuperUserRequiredMixin, generic.UpdateView):
model = Video
form_class = VideoEditForm
template_name = 'myadmin/video_edit.html'
def get_context_data(self, **kwargs):
context = super(VideoEditView, self).get_context_data(**kwargs)
clf_list = Classification.objects.all().values()
clf_data = {'clf_list':clf_list}
context.update(clf_data)
return context
def get_success_url(self):
messages.success(self.request, "保存成功")
return reverse('myadmin:video_edit', kwargs={'pk': self.kwargs['pk']})
@ajax_required
@require_http_methods(["POST"])
def video_delete(request):
if not request.user.is_superuser:
return JsonResponse({"code": 1, "msg": "无删除权限"})
video_id = request.POST['video_id']
instance = Video.objects.get(id=video_id)
instance.delete()
return JsonResponse({"code": 0, "msg": "success"})
class VideoListView(AdminUserRequiredMixin, generic.ListView):
model = Video
template_name = 'myadmin/video_list.html'
context_object_name = 'video_list'
paginate_by = 10
q = ''
def get_context_data(self, *, object_list=None, **kwargs):
context = super(VideoListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
def get_queryset(self):
self.q = self.request.GET.get("q", "")
return Video.objects.get_search_list(self.q)
class ClassificationListView(AdminUserRequiredMixin, generic.ListView):
model = Classification
template_name = 'myadmin/classification_list.html'
context_object_name = 'classification_list'
paginate_by = 10
q = ''
def get_context_data(self, *, object_list=None, **kwargs):
context = super(ClassificationListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
def get_queryset(self):
self.q = self.request.GET.get("q", "")
return Classification.objects.filter(title__contains=self.q)
class ClassificationAddView(SuperUserRequiredMixin, generic.View):
def get(self, request):
form = ClassificationAddForm()
return render(self.request, 'myadmin/classification_add.html', {'form': form})
def post(self, request):
form = ClassificationAddForm(data=request.POST)
if form.is_valid():
form.save(commit=True)
return render(self.request, 'myadmin/classification_add_success.html')
return render(self.request, 'myadmin/classification_add.html', {'form': form})
@ajax_required
@require_http_methods(["POST"])
def classification_delete(request):
if not request.user.is_superuser:
return JsonResponse({"code": 1, "msg": "无删除权限"})
classification_id = request.POST['classification_id']
instance = Classification.objects.get(id=classification_id)
instance.delete()
return JsonResponse({"code": 0, "msg": "success"})
class ClassificationEditView(SuperUserRequiredMixin, generic.UpdateView):
model = Classification
form_class = ClassificationEditForm
template_name = 'myadmin/classification_edit.html'
def get_success_url(self):
messages.success(self.request, "保存成功")
return reverse('myadmin:classification_edit', kwargs={'pk': self.kwargs['pk']})
class CommentListView(AdminUserRequiredMixin, generic.ListView):
model = Comment
template_name = 'myadmin/comment_list.html'
context_object_name = 'comment_list'
paginate_by = 10
q = ''
def get_context_data(self, *, object_list=None, **kwargs):
context = super(CommentListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
def get_queryset(self):
self.q = self.request.GET.get("q", "")
return Comment.objects.filter(content__contains=self.q).order_by('-timestamp')
@ajax_required
@require_http_methods(["POST"])
def comment_delete(request):
if not request.user.is_superuser:
return JsonResponse({"code": 1, "msg": "无删除权限"})
comment_id = request.POST['comment_id']
instance = Comment.objects.get(id=comment_id)
instance.delete()
return JsonResponse({"code": 0, "msg": "success"})
class UserListView(AdminUserRequiredMixin, generic.ListView):
model = User
template_name = 'myadmin/user_list.html'
context_object_name = 'user_list'
paginate_by = 10
q = ''
def get_context_data(self, *, object_list=None, **kwargs):
context = super(UserListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
def get_queryset(self):
self.q = self.request.GET.get("q", "")
return User.objects.filter(username__contains=self.q).order_by('-date_joined')
class UserAddView(SuperUserRequiredMixin, generic.View):
def get(self, request):
form = UserAddForm()
return render(self.request, 'myadmin/user_add.html', {'form': form})
def post(self, request):
form = UserAddForm(data=request.POST)
if form.is_valid():
user = form.save(commit=False)
password = form.cleaned_data.get('password')
user.set_password(password)
user.save()
return render(self.request, 'myadmin/user_add_success.html')
return render(self.request, 'myadmin/user_add.html', {'form': form})
class UserEditView(SuperUserRequiredMixin, generic.UpdateView):
model = User
form_class = UserEditForm
template_name = 'myadmin/user_edit.html'
def get_success_url(self):
messages.success(self.request, "保存成功")
return reverse('myadmin:user_edit', kwargs={'pk': self.kwargs['pk']})
@ajax_required
@require_http_methods(["POST"])
def user_delete(request):
if not request.user.is_superuser:
return JsonResponse({"code": 1, "msg": "无删除权限"})
user_id = request.POST['user_id']
instance = User.objects.get(id=user_id)
if instance.is_superuser:
return JsonResponse({"code": 1, "msg": "不能删除管理员"})
instance.delete()
return JsonResponse({"code": 0, "msg": "success"})
class SubscribeView(SuperUserRequiredMixin, generic.View):
def get(self, request):
video_list = Video.objects.get_published_list()
return render(request, "myadmin/subscribe.html" ,{'video_list':video_list})
def post(self, request):
if not request.user.is_superuser:
return JsonResponse({"code": 1, "msg": "无权限"})
video_id = request.POST['video_id']
video = Video.objects.get(id=video_id)
subject = video.title
context = {'video': video,'site_url':settings.SITE_URL}
html_message = render_to_string('myadmin/mail_template.html', context)
email_list = User.objects.filter(subscribe=True).values_list('email',flat=True)
# 分组
email_list = [email_list[i:i + 2] for i in range(0, len(email_list), 2)]
if email_list:
for to_list in email_list:
try:
send_html_email(subject, html_message, to_list)
except smtplib.SMTPException as e:
logger.error(e)
return JsonResponse({"code": 1, "msg": "发送失败"})
return JsonResponse({"code": 0, "msg": "success"})
else:
return JsonResponse({"code": 1, "msg": "邮件列表为空"})
class FeedbackListView(AdminUserRequiredMixin, generic.ListView):
model = Feedback
template_name = 'myadmin/feedback_list.html'
context_object_name = 'feedback_list'
paginate_by = 10
q = ''
def get_context_data(self, *, object_list=None, **kwargs):
context = super(FeedbackListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
def get_queryset(self):
self.q = self.request.GET.get("q", "")
return Feedback.objects.filter(content__contains=self.q).order_by('-timestamp')
@ajax_required
@require_http_methods(["POST"])
def feedback_delete(request):
if not request.user.is_superuser:
return JsonResponse({"code": 1, "msg": "无删除权限"})
feedback_id = request.POST['feedback_id']
instance = Feedback.objects.get(id=feedback_id)
instance.delete()
return JsonResponse({"code": 0, "msg": "success"})

@ -0,0 +1,6 @@
django-ratelimit==1.0.1
PyMySQL==1.0.2
django_chunked_upload==1.1.3
Django==3.2.11
sorl-thumbnail==12.8.0
pillow==9.1.1

@ -0,0 +1,95 @@
body {
display: relative;
}
.n{
display:none;
}
#logo{
margin-right: 16px;
}
#sidebar {
position: fixed;
top: 51.8px;
left: 0;
bottom: 0;
width: 18%;
background-color: #f5f5f5;
padding: 0px;
}
#sidebar .ui.menu {
margin: 2em 0 0;
font-size: 16px;
}
#sidebar .ui.menu > a.item {
color: #337ab7;
border-radius: 0 !important;
}
#sidebar .ui.menu > a.item.active {
background-color: #337ab7;
color: white;
border: none !important;
}
#sidebar .ui.menu > a.item:hover {
background-color: #4f93ce;
color: white;
}
#content {
margin-left: 19%;
width: 81%;
margin-top: 3em;
padding-left: 3em;
float: left;
}
#content > .ui.grid {
padding-right: 4em;
padding-bottom: 3em;
}
#content h1 {
font-size: 36px;
}
#content .ui.divider:not(.hidden) {
margin: 0;
}
#content table.ui.table {
border: none;
}
#content table.ui.table thead th {
border-bottom: 2px solid #eee !important;
}
.v-title-extra{
text-align: right;
}
.v-admin-search{
width: 50%;
}
#progress_layout{
margin-top:2rem;
}
.v-form-wrap {
width: 600px;
}
.v-form-wrap .form{
margin:2rem auto;
}
.v-form-field{
padding-top:1em;
padding-bottom:1em;
vertical-align:middle;
}

@ -0,0 +1,51 @@
.dropload-up,.dropload-down{
position: relative;
height: 0;
overflow: hidden;
font-size: 12px;
/* 开启硬件加速 */
-webkit-transform:translateZ(0);
transform:translateZ(0);
}
.dropload-down{
height: 50px;
}
.dropload-refresh,.dropload-update,.dropload-load,.dropload-noData{
height: 50px;
line-height: 50px;
text-align: center;
}
.dropload-load .loading{
display: inline-block;
height: 15px;
width: 15px;
border-radius: 100%;
margin: 6px;
border: 2px solid #666;
border-bottom-color: transparent;
vertical-align: middle;
-webkit-animation: rotate 0.75s linear infinite;
animation: rotate 0.75s linear infinite;
}
@-webkit-keyframes rotate {
0% {
-webkit-transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}

@ -0,0 +1,50 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

@ -0,0 +1,129 @@
.ui.tiny.images .image,
.ui.tiny.images img,
.ui.tiny.images svg,
.ui.tiny.image {
width: 180px;
height: auto;
font-size: 0.85714286rem;
}
.ui.sm.images .image,
.ui.sm.images img,
.ui.sm.images svg,
.ui.sm.image {
width: 100px;
height: auto;
font-size: 0.92857143rem;
}
*-------- pointer-events:none--------*
.ui.icon.input > i.icon:not(.link) {
}
/* Inline Label width已修改 */
.ui.form .inline.fields > label,
.ui.form .inline.fields .field > label,
.ui.form .inline.fields .field > p,
.ui.form .inline.field > label,
.ui.form .inline.field > p {
display: inline-block;
width: 20%;
margin-top: 0em;
margin-bottom: 0em;
vertical-align: baseline;
font-size: 0.92857143em;
font-weight: bold;
color: rgba(0, 0, 0, 0.87);
text-transform: none;
}
/* Inline Input widthauto-->70% */
.ui.form .inline.fields .field > input,
.ui.form .inline.fields .field > select,
.ui.form .inline.field > input,
.ui.form .inline.field > select {
display: inline-block;
width: 70%;
margin-top: 0em;
margin-bottom: 0em;
vertical-align: middle;
font-size: 1em;
}
/*--- Icon ---*/
.ui.vertical.menu .item > i.icon {
width: 1.18em;
float: none;
margin: 0em 0em 0em 0.5em;
}
.ui.card {
margin: 1em auto;
}
.ui.comments .comment .avatar {
display: block;
width: 2.5em;
height: 2.5em;
float: left;
margin: 0.2em 0em 0em;
}
/*--------------
border-radius: 0.25rem;
---------------*/
.ui.comments .comment img.avatar,
.ui.comments .comment .avatar img {
display: block;
margin: 0em auto;
width: 100%;
height: 100%;
}
@media only screen and (max-width: 767px) {
.ui.unstackable.items > .item > .image,
.ui.unstackable.items > .item > .image > img {
width: 80px !important;
}
}
.ui.checkbox {
position: relative;
display: inline-block;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
outline: none;
vertical-align: middle;
font-style: normal;
min-height: 17px;
font-size: 1rem;
line-height: 17px;
min-width: 17px;
}
.ui.dropdown > .text > img,
.ui.dropdown > .text > .image,
.ui.dropdown .menu > .item > .image,
.ui.dropdown .menu > .item > img {
display: inline-block;
vertical-align: middle;
width: auto;
margin-top: -0.5em;
margin-bottom: -0.5em;
max-height: 2em;
}

@ -0,0 +1,208 @@
body {
margin:0px;
background-color: #FFFFFF;
}
.color-black{
color:#000000;
}
.cursor{
cursor: pointer;
}
.n{
display:none;
}
.fl{
float:left;
}
.fr{
float:right;
}
.v-inline-middle{
display:inline-block;
vertical-align: middle;
}
#v-content{
padding-top:5.2em;
}
#v-account-body{
background-color: #DADADA;
}
#v-account-body > .grid{
height: 100%;
}
.ui.menu .item img.logo {
margin-right: 0.66rem;
}
.ui.footer.segment {
margin: 2em 0em 0em;
padding: 1em 0em;
}
.v-account {
max-width: 450px;
}
.video{
width: 100%;
height: auto;
}
.v-video-search{
margin-right: 3em;
width: 30%;
}
.v-video-search i{
cursor: pointer;
}
.v-container-top{
height: 5.66em;
}
.v-header-extra{
position: relative;
width:100%;
text-align: right;
padding-top: 0.60rem;
}
.v-play-icon{
position: absolute;
left: 1.0rem;
bottom: 1.0rem;
color: #fff;
}
.ui .card .image{
cursor: pointer;
}
.v-icon{
float: left;
}
.video-duration{
position: absolute;
bottom: 0px;
left: 4px;
color: #fff;
}
.header-title{
margin-top: 30px;
font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-weight: bold;
font-size: 1.28571429em;
color: rgba(0, 0, 0, 0.85);
}
.video-title{
color: var(--yt-primary-text-color);
font-size: 1.28em;
font-weight: 400;
line-height: 1em;
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
}
.video-side-title{
color: var(--yt-primary-text-color);
font-size: 1.28em;
font-weight: 400;
line-height: 1em;
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
}
.video-view-count{
margin-top: 16px;
color: #888888;
}
.video-view-operation{
margin-top: 16px;
color: #888888;
}
.video-info{
margin-top: 20px;
}
.video-page{
display: block;
margin:1.28em;
text-align: center;
}
.v-settings{
margin-top: 40px;
}
.v-settings-side{
border-right: 1px solid #e3e3e3;
}
.v-settings-content .form{
margin:0em auto;
width: 400px;
}
.v-settings-nav{
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 185px;
min-height: 350px;
margin-right: 100px;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.v-settings-avatar{
display:inline-block;
float:left;
cursor:pointer;
}
.v-nav-title{
margin-left:1.0em;
}
.v-form-field{
padding-top:1em;
padding-bottom:1em;
vertical-align:middle;
}
.v-form-fix-avatar{
padding:2em;
}
.v-header-avatar{
line-height:34px;
color:#000;
}
.del {
width:100px;
height: 40px;
position: absolute;
right: 40px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -0,0 +1,32 @@
function getCookie(name) {
// Function to get any cookie available in the session.
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
};
function csrfSafeMethod(method) {
// These HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
var csrftoken = getCookie('csrftoken');
var page_title = $(document).attr("title");
// This sets up every ajax call with proper headers.
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});

@ -0,0 +1,108 @@
$(function () {
// 写入csrf
$.getScript("/static/js/csrftoken.js");
// 点赞
$("#like").click(function(){
var video_id = $("#like").attr("video-id");
$.ajax({
url: '/video/like/',
data: {
video_id: video_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
var code = data.code
if(code == 0){
var likes = data.likes
var user_liked = data.user_liked
$('#like-count').text(likes)
if(user_liked == 0){
$('#like').removeClass("grey").addClass("red")
}else{
$('#like').removeClass("red").addClass("grey")
}
}else{
var msg = data.msg
alert(msg)
}
},
error: function(data){
alert("点赞失败")
}
});
});
// 收藏
$("#star").click(function(){
var video_id = $("#star").attr("video-id");
$.ajax({
url: '/video/collect/',
data: {
video_id: video_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
var code = data.code
if(code == 0){
var collects = data.collects
var user_collected = data.user_collected
$('#collect-count').text(collects)
if(user_collected == 0){
$('#star').removeClass("grey").addClass("red")
}else{
$('#star').removeClass("red").addClass("grey")
}
}else{
var msg = data.msg
alert(msg)
}
},
error: function(data){
alert("收藏失败")
}
});
});
// 提交评论
var frm = $('#comment_form')
frm.submit(function () {
$.ajax({
type: frm.attr('method'),
url: frm.attr('action'),
dataType:'json',
data: frm.serialize(),
success: function (data) {
var code = data.code
var msg = data.msg
if(code == 0){
$('#id_content').val("")
$('.comment-list').prepend(data.html);
$('#comment-result').text("评论成功")
$('.info').show().delay(2000).fadeOut(800)
}else{
$('#comment-result').text(msg)
$('.info').show().delay(2000).fadeOut(800);
}
},
error: function(data) {
}
});
return false;
});
})

@ -0,0 +1,316 @@
/**
* dropload
* 西门(http://ons.me/526.html)
* 0.9.1(161205)
*/
;(function($){
'use strict';
var win = window;
var doc = document;
var $win = $(win);
var $doc = $(doc);
$.fn.dropload = function(options){
return new MyDropLoad(this, options);
};
var MyDropLoad = function(element, options){
var me = this;
me.$element = element;
// 上方是否插入DOM
me.upInsertDOM = false;
// loading状态
me.loading = false;
// 是否锁定
me.isLockUp = false;
me.isLockDown = false;
// 是否有数据
me.isData = true;
me._scrollTop = 0;
me._threshold = 0;
me.init(options);
};
// 初始化
MyDropLoad.prototype.init = function(options){
var me = this;
me.opts = $.extend(true, {}, {
scrollArea : me.$element, // 滑动区域
domUp : { // 上方DOM
domClass : 'dropload-up',
domRefresh : '<div class="dropload-refresh">↓下拉刷新</div>',
domUpdate : '<div class="dropload-update">↑释放更新</div>',
domLoad : '<div class="dropload-load"><span class="loading"></span>加载中...</div>'
},
domDown : { // 下方DOM
domClass : 'dropload-down',
domRefresh : '<div class="dropload-refresh">↑上拉加载更多</div>',
domLoad : '<div class="dropload-load"><span class="loading"></span>加载中...</div>',
domNoData : '<div class="dropload-noData">-- END --</div>'
},
autoLoad : true, // 自动加载
distance : 50, // 拉动距离
threshold : '', // 提前加载距离
loadUpFn : '', // 上方function
loadDownFn : '' // 下方function
}, options);
// 如果加载下方事先在下方插入DOM
if(me.opts.loadDownFn != ''){
me.$element.append('<div class="'+me.opts.domDown.domClass+'">'+me.opts.domDown.domRefresh+'</div>');
me.$domDown = $('.'+me.opts.domDown.domClass);
}
// 计算提前加载距离
if(!!me.$domDown && me.opts.threshold === ''){
// 默认滑到加载区2/3处时加载
me._threshold = Math.floor(me.$domDown.height()*1/3);
}else{
me._threshold = me.opts.threshold;
}
// 判断滚动区域
if(me.opts.scrollArea == win){
me.$scrollArea = $win;
// 获取文档高度
me._scrollContentHeight = $doc.height();
// 获取win显示区高度 —— 这里有坑
me._scrollWindowHeight = doc.documentElement.clientHeight;
}else{
me.$scrollArea = me.opts.scrollArea;
me._scrollContentHeight = me.$element[0].scrollHeight;
me._scrollWindowHeight = me.$element.height();
}
fnAutoLoad(me);
// 窗口调整
$win.on('resize',function(){
clearTimeout(me.timer);
me.timer = setTimeout(function(){
if(me.opts.scrollArea == win){
// 重新获取win显示区高度
me._scrollWindowHeight = win.innerHeight;
}else{
me._scrollWindowHeight = me.$element.height();
}
fnAutoLoad(me);
},150);
});
// 绑定触摸
me.$element.on('touchstart',function(e){
if(!me.loading){
fnTouches(e);
fnTouchstart(e, me);
}
});
me.$element.on('touchmove',function(e){
if(!me.loading){
fnTouches(e, me);
fnTouchmove(e, me);
}
});
me.$element.on('touchend',function(){
if(!me.loading){
fnTouchend(me);
}
});
// 加载下方
me.$scrollArea.on('scroll',function(){
me._scrollTop = me.$scrollArea.scrollTop();
// 滚动页面触发加载数据
if(me.opts.loadDownFn != '' && !me.loading && !me.isLockDown && (me._scrollContentHeight - me._threshold) <= (me._scrollWindowHeight + me._scrollTop)){
loadDown(me);
}
});
// 提前加载第一页
loadDown(me);
};
// touches
function fnTouches(e){
if(!e.touches){
e.touches = e.originalEvent.touches;
}
}
// touchstart
function fnTouchstart(e, me){
me._startY = e.touches[0].pageY;
// 记住触摸时的scrolltop值
me.touchScrollTop = me.$scrollArea.scrollTop();
}
// touchmove
function fnTouchmove(e, me){
me._curY = e.touches[0].pageY;
me._moveY = me._curY - me._startY;
if(me._moveY > 0){
me.direction = 'down';
}else if(me._moveY < 0){
me.direction = 'up';
}
var _absMoveY = Math.abs(me._moveY);
// 加载上方
if(me.opts.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'down' && !me.isLockUp){
e.preventDefault();
me.$domUp = $('.'+me.opts.domUp.domClass);
// 如果加载区没有DOM
if(!me.upInsertDOM){
me.$element.prepend('<div class="'+me.opts.domUp.domClass+'"></div>');
me.upInsertDOM = true;
}
fnTransition(me.$domUp,0);
// 下拉
if(_absMoveY <= me.opts.distance){
me._offsetY = _absMoveY;
// todomove时会不断清空、增加dom有可能影响性能下同
me.$domUp.html(me.opts.domUp.domRefresh);
// 指定距离 < 下拉距离 < 指定距离*2
}else if(_absMoveY > me.opts.distance && _absMoveY <= me.opts.distance*2){
me._offsetY = me.opts.distance+(_absMoveY-me.opts.distance)*0.5;
me.$domUp.html(me.opts.domUp.domUpdate);
// 下拉距离 > 指定距离*2
}else{
me._offsetY = me.opts.distance+me.opts.distance*0.5+(_absMoveY-me.opts.distance*2)*0.2;
}
me.$domUp.css({'height': me._offsetY});
}
}
// touchend
function fnTouchend(me){
var _absMoveY = Math.abs(me._moveY);
if(me.opts.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'down' && !me.isLockUp){
fnTransition(me.$domUp,300);
if(_absMoveY > me.opts.distance){
me.$domUp.css({'height':me.$domUp.children().height()});
me.$domUp.html(me.opts.domUp.domLoad);
me.loading = true;
me.opts.loadUpFn(me);
}else{
me.$domUp.css({'height':'0'}).on('webkitTransitionEnd mozTransitionEnd transitionend',function(){
me.upInsertDOM = false;
$(this).remove();
});
}
me._moveY = 0;
}
}
// 如果文档高度不大于窗口高度,数据较少,自动加载下方数据
function fnAutoLoad(me){
if(me.opts.loadDownFn != '' && me.opts.autoLoad){
if((me._scrollContentHeight - me._threshold) <= me._scrollWindowHeight){
loadDown(me);
}
}
}
// 重新获取文档高度
function fnRecoverContentHeight(me){
if(me.opts.scrollArea == win){
me._scrollContentHeight = $doc.height();
}else{
me._scrollContentHeight = me.$element[0].scrollHeight;
}
}
// 加载下方
function loadDown(me){
me.direction = 'up';
me.$domDown.html(me.opts.domDown.domLoad);
me.loading = true;
me.opts.loadDownFn(me);
}
// 锁定
MyDropLoad.prototype.lock = function(direction){
var me = this;
// 如果不指定方向
if(direction === undefined){
// 如果操作方向向上
if(me.direction == 'up'){
me.isLockDown = true;
// 如果操作方向向下
}else if(me.direction == 'down'){
me.isLockUp = true;
}else{
me.isLockUp = true;
me.isLockDown = true;
}
// 如果指定锁上方
}else if(direction == 'up'){
me.isLockUp = true;
// 如果指定锁下方
}else if(direction == 'down'){
me.isLockDown = true;
// 为了解决DEMO5中tab效果bug因为滑动到下面再滑上去点tabdirection=down所以有bug
me.direction = 'up';
}
};
// 解锁
MyDropLoad.prototype.unlock = function(){
var me = this;
// 简单粗暴解锁
me.isLockUp = false;
me.isLockDown = false;
// 为了解决DEMO5中tab效果bug因为滑动到下面再滑上去点tabdirection=down所以有bug
me.direction = 'up';
};
// 无数据
MyDropLoad.prototype.noData = function(flag){
var me = this;
if(flag === undefined || flag == true){
me.isData = false;
}else if(flag == false){
me.isData = true;
}
};
// 重置
MyDropLoad.prototype.resetload = function(){
var me = this;
if(me.direction == 'down' && me.upInsertDOM){
me.$domUp.css({'height':'0'}).on('webkitTransitionEnd mozTransitionEnd transitionend',function(){
me.loading = false;
me.upInsertDOM = false;
$(this).remove();
fnRecoverContentHeight(me);
});
}else if(me.direction == 'up'){
me.loading = false;
// 如果有数据
if(me.isData){
// 加载区修改样式
me.$domDown.html(me.opts.domDown.domRefresh);
fnRecoverContentHeight(me);
fnAutoLoad(me);
}else{
// 如果没数据
me.$domDown.html(me.opts.domDown.domNoData);
}
}
};
// css过渡
function fnTransition(dom,num){
dom.css({
'-webkit-transition':'all '+num+'ms',
'transition':'all '+num+'ms'
});
}
})(window.Zepto || window.jQuery);

@ -0,0 +1,49 @@
$(function(){
// 头像dropdown
$('#v-header-avatar').dropdown();
$('#v-search').bind('keypress',function(event){
var word = $('#v-search').val()
if(event.keyCode == "13" && word.length > 0)
{
window.location = search_url + '?q='+word;
}
});
$('#search').click(function(){
var word = $('#v-search').val()
if(word.length > 0){
window.location = search_url + '?q='+word;
}
});
var explorerName = getExploreName();
if(explorerName.startsWith("Safari")||explorerName.startsWith("IE")){
alert("请使用Chrome浏览器访问该网站");
}
function getExploreName(){
var userAgent = navigator.userAgent;
if(userAgent.indexOf("Opera") > -1 || userAgent.indexOf("OPR") > -1){
return 'Opera';
}else if(userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1){
return 'IE';
}else if(userAgent.indexOf("Edge") > -1){
return 'Edge';
}else if(userAgent.indexOf("Firefox") > -1){
return 'Firefox';
}else if(userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") == -1){
return 'Safari';
}else if(userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Safari") > -1){
return 'Chrome';
}else if(!!window.ActiveXObject || "ActiveXObject" in window){
return 'IE>=11';
}else{
return 'Unkonwn';
}
}
});

@ -0,0 +1,16 @@
$(function(){
var pathname = window.location.pathname;
console.log(pathname);
if(pathname.indexOf("users/profile/") >= 0 ) {
$("#id_profile").addClass("active");
}
if(pathname.indexOf("users/subscribe/") >= 0 ) {
$("#id_subscribe").addClass("active");
}
if(pathname.indexOf("users/change_password/") >= 0 ) {
$("#id_password").addClass("active");
}
if(pathname.indexOf("users/feedback/") >= 0 ) {
$("#id_feedback").addClass("active");
}
});

@ -0,0 +1,42 @@
$(function(){
// 页数
var page = 0;
// 每页展示15个
var page_size = 15;
// dropload
$('.comments').dropload({
scrollArea : window,
loadDownFn : function(me){
page++;
$.ajax({
type: 'GET',
url: comments_url,
data:{
video_id: video_id,
page: page,
page_size: page_size
},
dataType: 'json',
success: function(data){
var code = data.code
var count = data.comment_count
if(code == 0){
$('#id_comment_label').text(count + "条评论");
$('.comment-list').append(data.html);
me.resetload();
}else{
me.lock();
me.noData();
me.resetload();
}
},
error: function(xhr, type){
me.resetload();
}
});
}
});
});

@ -0,0 +1,36 @@
//$(document)
// .ready(function() {
// $('.ui.form')
// .form({
// fields: {
// username: {
// identifier : 'username',
// rules: [
// {
// type : 'empty',
// prompt : '请输入用户名'
// },
// {
// type : 'empty',
// prompt : '用户名不能为空'
// }
// ]
// },
// password: {
// identifier : 'password',
// rules: [
// {
// type : 'empty',
// prompt : '请输入密码'
// },
// {
// type : 'length[6]',
// prompt : '密码必须六位数以上'
// }
// ]
// }
// }
// })
// ;
// })
// ;

@ -0,0 +1,46 @@
$(function(){
var pathname = window.location.pathname;
console.log(pathname);
if(pathname.endsWith("myadmin/")) {
$("#index").addClass("active");
}
if(pathname.endsWith("myadmin/video_list/")) {
$("#video_list").addClass("active");
}
if(pathname.indexOf("myadmin/video_edit/") >= 0){
$("#video_list").addClass("active");
}
if(pathname.endsWith("myadmin/video_add/")) {
$("#video_add").addClass("active");
}
if(pathname.endsWith("myadmin/classification_list/")) {
$("#classification_list").addClass("active");
}
if(pathname.indexOf("myadmin/classification_edit/") >= 0){
$("#classification_list").addClass("active");
}
if(pathname.endsWith("myadmin/classification_add/")) {
$("#classification_add").addClass("active");
}
if(pathname.endsWith("myadmin/user_list/")) {
$("#user_list").addClass("active");
}
if(pathname.indexOf("myadmin/user_edit/") >= 0){
$("#user_list").addClass("active");
}
if(pathname.endsWith("myadmin/user_add/")) {
$("#user_add").addClass("active");
}
if(pathname.endsWith("myadmin/comment_list/")) {
$("#comment_list").addClass("active");
}
if(pathname.indexOf("myadmin/setting/") >= 0) {
$("#setting").addClass("active");
}
if(pathname.indexOf("myadmin/subscribe/") >= 0){
$("#subscribe").addClass("active");
}
if(pathname.indexOf("myadmin/feedback_list/") >= 0){
$("#feedback_list").addClass("active");
}
});

@ -0,0 +1,59 @@
// 写入csrf
$.getScript("/static/js/csrftoken.js");
$('.classification-delete').click(function(){
var tr = $(this).closest("tr");
var classification_id = $(tr).attr("classification-id");
$('.ui.tiny.modal.delete')
.modal({
closable : true,
onDeny : function(){
return true;
},
onApprove : function() {
$.ajax({
url: api_classification_delete,
data: {
'classification_id':classification_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
console.log(data);
var code = data.code
var msg = data.msg
if(code == 0){
window.location.reload();
}else{
alert(msg);
}
},
error: function(data){
alert("error"+data)
}
});
}
})
.modal('show');
});
// search
$('#v-search').bind('keypress',function(event){
var word = $('#v-search').val()
if(event.keyCode == "13" && word.length > 0)
{
window.location = search_url + '?q='+word;
}
});
$('#search').click(function(){
var word = $('#v-search').val()
if(word.length > 0){
window.location = search_url + '?q='+word;
}
})

@ -0,0 +1,59 @@
// 写入csrf
$.getScript("/static/js/csrftoken.js");
$('.comment-delete').click(function(){
var tr = $(this).closest("tr");
var comment_id = $(tr).attr("comment-id");
$('.ui.tiny.modal.delete')
.modal({
closable : true,
onDeny : function(){
return true;
},
onApprove : function() {
$.ajax({
url: api_comment_delete,
data: {
'comment_id':comment_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
console.log(data);
var code = data.code
var msg = data.msg
if(code == 0){
window.location.reload();
}else{
alert(msg);
}
},
error: function(data){
alert("error"+data)
}
});
}
})
.modal('show');
});
// search
$('#v-search').bind('keypress',function(event){
var word = $('#v-search').val()
if(event.keyCode == "13" && word.length > 0)
{
window.location = search_url + '?q='+word;
}
});
$('#search').click(function(){
var word = $('#v-search').val()
if(word.length > 0){
window.location = search_url + '?q='+word;
}
})

@ -0,0 +1,59 @@
// 写入csrf
$.getScript("/static/js/csrftoken.js");
$('.feedback-delete').click(function(){
var tr = $(this).closest("tr");
var feedback_id = $(tr).attr("feedback-id");
$('.ui.tiny.modal.delete')
.modal({
closable : true,
onDeny : function(){
return true;
},
onApprove : function() {
$.ajax({
url: api_feedback_delete,
data: {
'feedback_id':feedback_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
console.log(data);
var code = data.code
var msg = data.msg
if(code == 0){
window.location.reload();
}else{
alert(msg);
}
},
error: function(data){
alert("error"+data)
}
});
}
})
.modal('show');
});
// search
$('#v-search').bind('keypress',function(event){
var word = $('#v-search').val()
if(event.keyCode == "13" && word.length > 0)
{
window.location = search_url + '?q='+word;
}
});
$('#search').click(function(){
var word = $('#v-search').val()
if(word.length > 0){
window.location = search_url + '?q='+word;
}
})

@ -0,0 +1,37 @@
// 写入csrf
$.getScript("/static/js/csrftoken.js");
$('#send-mail').click(function(){
var video_id = $('.selection.dropdown').dropdown('get value');
if(video_id == ''){
alert("不能为空");
return;
}
$('#send-mail-progress').text('正在发送通知...')
$.ajax({
url: api_send_mail,
data: {
'video_id':video_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
console.log(data);
var code = data.code
var msg = data.msg
if(code == 0){
$('#send-mail-progress').text('发送成功')
}else{
$('#send-mail-progress').text(msg)
}
},
error: function(data){
$('#send-mail-progress').text('发送失败')
}
});
});

@ -0,0 +1,58 @@
// 写入csrf
$.getScript("/static/js/csrftoken.js");
$('.user-delete').click(function(){
var tr = $(this).closest("tr");
var user_id = $(tr).attr("user-id");
$('.ui.tiny.modal.delete')
.modal({
closable : true,
onDeny : function(){
return true;
},
onApprove : function() {
$.ajax({
url: api_user_delete,
data: {
'user_id':user_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
console.log(data);
var code = data.code
if(code == 0){
window.location.reload();
}else{
alert(""+data.msg)
}
},
error: function(data){
alert("error"+data)
}
});
}
})
.modal('show');
});
// search
$('#v-search').bind('keypress',function(event){
var word = $('#v-search').val()
if(event.keyCode == "13" && word.length > 0)
{
window.location = search_url + '?q='+word;
}
});
$('#search').click(function(){
var word = $('#v-search').val()
if(word.length > 0){
window.location = search_url + '?q='+word;
}
})

@ -0,0 +1,60 @@
// 写入csrf
$.getScript("/static/js/csrftoken.js");
// 删除
$(".video-list").on("click", ".video-delete", function () {
var tr = $(this).closest("tr");
var video_id = $(tr).attr("video-id");
$('.ui.tiny.modal.delete')
.modal({
closable : true,
onDeny : function(){
return true;
},
onApprove : function() {
$.ajax({
url: api_video_delete,
data: {
'video_id':video_id,
'csrf_token': csrftoken
},
type: 'POST',
dataType: 'json',
success: function (data) {
console.log(data);
var code = data.code
var msg = data.msg
if(code == 0){
window.location.reload();
}else{
alert(msg)
}
},
error: function(data){
alert("error"+data)
}
});
}
})
.modal('show');
});
// search
$('#v-search').bind('keypress',function(event){
var word = $('#v-search').val()
if(event.keyCode == "13" && word.length > 0)
{
window.location = search_url + '?q='+word;
}
});
$('#search').click(function(){
var word = $('#v-search').val()
if(word.length > 0){
window.location = search_url + '?q='+word;
}
})

@ -0,0 +1,110 @@
/**
* https://github.com/juliomalegria/django-chunked-upload
*/
$(function () {
var md5 = "",
lastprogress = 0,
csrf = $("input[name='csrfmiddlewaretoken']")[0].value,
form_data = [{"name": "csrfmiddlewaretoken", "value": csrf}];
function calculate_md5(file, chunk_size) {
var slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
chunks = chunks = Math.ceil(file.size / chunk_size),
current_chunk = 0,
spark = new SparkMD5.ArrayBuffer();
function onload(e) {
spark.append(e.target.result); // append chunk
current_chunk++;
if (current_chunk < chunks) {
read_next_chunk();
} else {
md5 = spark.end();
}
};
function read_next_chunk() {
var reader = new FileReader();
reader.onload = onload;
var start = current_chunk * chunk_size,
end = Math.min(start + chunk_size, file.size);
reader.readAsArrayBuffer(slice.call(file, start, end));
};
read_next_chunk();
}
$("#chunked_upload").fileupload({
url: api_chunked_uplad,
dataType: "json",
maxChunkSize: 100000, // Chunks of 100 kB
formData: form_data,
add: function(e, data) { // Called before starting upload
var fileSize = data.originalFiles[0]['size'];
var type = data.originalFiles[0]['type'];
console.log('file size --> ' + fileSize);
console.log('type --> ' + type);
// 若想限制大小,解开注释即可
// if(fileSize > 10000000000){
// alert('文件太大了请上传10000M以内的文件');
// return;
// }
if(!type.startsWith("video/")){
alert('视频格式不正确');
return;
}
// If this is the second file you're uploading we need to remove the
// old upload_id and just keep the csrftoken (which is always first).
form_data.splice(1);
calculate_md5(data.files[0], 100000); // Again, chunks of 100 kB
data.submit();
$('#progress_label').on('click', false);
$('#progress_layout').show()
},
chunkdone: function (e, data) { // Called after uploading each chunk
if (form_data.length < 2) {
form_data.push(
{"name": "upload_id", "value": data.result.upload_id}
);
}
var progress = parseInt(data.loaded / data.total * 100.0, 10);
console.log(progress);
if(progress > lastprogress){
lastprogress = progress
$('#upload_progress').progress({
percent: progress
});
}
},
done: function (e, data) { // Called when the file has completely uploaded
$.ajax({
type: "POST",
url: api_chunked_upload_complete,
data: {
csrfmiddlewaretoken: csrf,
upload_id: data.result.upload_id,
md5: md5
},
dataType: "json",
success: function(data) {
console.log(data)
$('#upload_label').text('上传成功');
$('#upload_progress').progress({
percent: 100
});
$('#next_layout').show();
$('#next').click(function(){
window.location = '/myadmin/video_publish/' + data.video_id
});
}
});
},
});
})

File diff suppressed because it is too large Load Diff

@ -0,0 +1,217 @@
/*
* jQuery Iframe Transport Plugin 1.8.3
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* global define, require, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS:
factory(require('jquery'));
} else {
// Browser globals:
factory(window.jQuery);
}
}(function ($) {
'use strict';
// Helper variable to create unique names for the transport iframes:
var counter = 0;
// The iframe transport accepts four additional options:
// options.fileInput: a jQuery collection of file input fields
// options.paramName: the parameter name for the file form data,
// overrides the name property of the file input field(s),
// can be a string or an array of strings.
// options.formData: an array of objects with name and value properties,
// equivalent to the return data of .serializeArray(), e.g.:
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
// options.initialIframeSrc: the URL of the initial iframe src,
// by default set to "javascript:false;"
$.ajaxTransport('iframe', function (options) {
if (options.async) {
// javascript:false as initial iframe src
// prevents warning popups on HTTPS in IE6:
/*jshint scripturl: true */
var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
/*jshint scripturl: false */
form,
iframe,
addParamChar;
return {
send: function (_, completeCallback) {
form = $('<form style="display:none;"></form>');
form.attr('accept-charset', options.formAcceptCharset);
addParamChar = /\?/.test(options.url) ? '&' : '?';
// XDomainRequest only supports GET and POST:
if (options.type === 'DELETE') {
options.url = options.url + addParamChar + '_method=DELETE';
options.type = 'POST';
} else if (options.type === 'PUT') {
options.url = options.url + addParamChar + '_method=PUT';
options.type = 'POST';
} else if (options.type === 'PATCH') {
options.url = options.url + addParamChar + '_method=PATCH';
options.type = 'POST';
}
// IE versions below IE8 cannot set the name property of
// elements that have already been added to the DOM,
// so we set the name along with the iframe HTML markup:
counter += 1;
iframe = $(
'<iframe src="' + initialIframeSrc +
'" name="iframe-transport-' + counter + '"></iframe>'
).bind('load', function () {
var fileInputClones,
paramNames = $.isArray(options.paramName) ?
options.paramName : [options.paramName];
iframe
.unbind('load')
.bind('load', function () {
var response;
// Wrap in a try/catch block to catch exceptions thrown
// when trying to access cross-domain iframe contents:
try {
response = iframe.contents();
// Google Chrome and Firefox do not throw an
// exception when calling iframe.contents() on
// cross-domain requests, so we unify the response:
if (!response.length || !response[0].firstChild) {
throw new Error();
}
} catch (e) {
response = undefined;
}
// The complete callback returns the
// iframe content document as response object:
completeCallback(
200,
'success',
{'iframe': response}
);
// Fix for IE endless progress bar activity bug
// (happens on form submits to iframe targets):
$('<iframe src="' + initialIframeSrc + '"></iframe>')
.appendTo(form);
window.setTimeout(function () {
// Removing the form in a setTimeout call
// allows Chrome's developer tools to display
// the response result
form.remove();
}, 0);
});
form
.prop('target', iframe.prop('name'))
.prop('action', options.url)
.prop('method', options.type);
if (options.formData) {
$.each(options.formData, function (index, field) {
$('<input type="hidden"/>')
.prop('name', field.name)
.val(field.value)
.appendTo(form);
});
}
if (options.fileInput && options.fileInput.length &&
options.type === 'POST') {
fileInputClones = options.fileInput.clone();
// Insert a clone for each file input field:
options.fileInput.after(function (index) {
return fileInputClones[index];
});
if (options.paramName) {
options.fileInput.each(function (index) {
$(this).prop(
'name',
paramNames[index] || options.paramName
);
});
}
// Appending the file input fields to the hidden form
// removes them from their original location:
form
.append(options.fileInput)
.prop('enctype', 'multipart/form-data')
// enctype must be set as encoding for IE:
.prop('encoding', 'multipart/form-data');
// Remove the HTML5 form attribute from the input(s):
options.fileInput.removeAttr('form');
}
form.submit();
// Insert the file input fields at their original location
// by replacing the clones with the originals:
if (fileInputClones && fileInputClones.length) {
options.fileInput.each(function (index, input) {
var clone = $(fileInputClones[index]);
// Restore the original name and form properties:
$(input)
.prop('name', clone.prop('name'))
.attr('form', clone.attr('form'));
clone.replaceWith(input);
});
}
});
form.append(iframe).appendTo(document.body);
},
abort: function () {
if (iframe) {
// javascript:false as iframe src aborts the request
// and prevents warning popups on HTTPS in IE6.
// concat is used to avoid the "Script URL" JSLint error:
iframe
.unbind('load')
.prop('src', initialIframeSrc);
}
if (form) {
form.remove();
}
}
};
}
});
// The iframe transport returns the iframe content document as response.
// The following adds converters from iframe to text, json, html, xml
// and script.
// Please note that the Content-Type for JSON responses has to be text/plain
// or text/html, if the browser doesn't include application/json in the
// Accept header, else IE will show a download dialog.
// The Content-Type for XML responses on the other hand has to be always
// application/xml or text/xml, so IE properly parses the XML response.
// See also
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
$.ajaxSetup({
converters: {
'iframe text': function (iframe) {
return iframe && $(iframe[0].body).text();
},
'iframe json': function (iframe) {
return iframe && $.parseJSON($(iframe[0].body).text());
},
'iframe html': function (iframe) {
return iframe && $(iframe[0].body).html();
},
'iframe xml': function (iframe) {
var xmlDoc = iframe && iframe[0];
return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
$.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
$(xmlDoc.body).html());
},
'iframe script': function (iframe) {
return iframe && $.globalEval($(iframe[0].body).text());
}
}
});
}));

File diff suppressed because one or more lines are too long

@ -0,0 +1,563 @@
/*! jQuery UI - v1.11.1+CommonJS - 2014-09-17
* http://jqueryui.com
* Includes: widget.js
* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
(function( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define([ "jquery" ], factory );
} else if (typeof exports === "object") {
// Node/CommonJS:
factory(require("jquery"));
} else {
// Browser globals
factory( jQuery );
}
}(function( $ ) {
/*!
* jQuery UI Widget 1.11.1
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/jQuery.widget/
*/
var widget_uuid = 0,
widget_slice = Array.prototype.slice;
$.cleanData = (function( orig ) {
return function( elems ) {
var events, elem, i;
for ( i = 0; (elem = elems[i]) != null; i++ ) {
try {
// Only trigger remove when necessary to save time
events = $._data( elem, "events" );
if ( events && events.remove ) {
$( elem ).triggerHandler( "remove" );
}
// http://bugs.jquery.com/ticket/8235
} catch( e ) {}
}
orig( elems );
};
})( $.cleanData );
$.widget = function( name, base, prototype ) {
var fullName, existingConstructor, constructor, basePrototype,
// proxiedPrototype allows the provided prototype to remain unmodified
// so that it can be used as a mixin for multiple widgets (#8876)
proxiedPrototype = {},
namespace = name.split( "." )[ 0 ];
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
if ( !prototype ) {
prototype = base;
base = $.Widget;
}
// create selector for plugin
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
return !!$.data( elem, fullName );
};
$[ namespace ] = $[ namespace ] || {};
existingConstructor = $[ namespace ][ name ];
constructor = $[ namespace ][ name ] = function( options, element ) {
// allow instantiation without "new" keyword
if ( !this._createWidget ) {
return new constructor( options, element );
}
// allow instantiation without initializing for simple inheritance
// must use "new" keyword (the code above always passes args)
if ( arguments.length ) {
this._createWidget( options, element );
}
};
// extend with the existing constructor to carry over any static properties
$.extend( constructor, existingConstructor, {
version: prototype.version,
// copy the object used to create the prototype in case we need to
// redefine the widget later
_proto: $.extend( {}, prototype ),
// track widgets that inherit from this widget in case this widget is
// redefined after a widget inherits from it
_childConstructors: []
});
basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
basePrototype.options = $.widget.extend( {}, basePrototype.options );
$.each( prototype, function( prop, value ) {
if ( !$.isFunction( value ) ) {
proxiedPrototype[ prop ] = value;
return;
}
proxiedPrototype[ prop ] = (function() {
var _super = function() {
return base.prototype[ prop ].apply( this, arguments );
},
_superApply = function( args ) {
return base.prototype[ prop ].apply( this, args );
};
return function() {
var __super = this._super,
__superApply = this._superApply,
returnValue;
this._super = _super;
this._superApply = _superApply;
returnValue = value.apply( this, arguments );
this._super = __super;
this._superApply = __superApply;
return returnValue;
};
})();
});
constructor.prototype = $.widget.extend( basePrototype, {
// TODO: remove support for widgetEventPrefix
// always use the name + a colon as the prefix, e.g., draggable:start
// don't prefix for widgets that aren't DOM-based
widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
}, proxiedPrototype, {
constructor: constructor,
namespace: namespace,
widgetName: name,
widgetFullName: fullName
});
// If this widget is being redefined then we need to find all widgets that
// are inheriting from it and redefine all of them so that they inherit from
// the new version of this widget. We're essentially trying to replace one
// level in the prototype chain.
if ( existingConstructor ) {
$.each( existingConstructor._childConstructors, function( i, child ) {
var childPrototype = child.prototype;
// redefine the child widget using the same prototype that was
// originally used, but inherit from the new version of the base
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
});
// remove the list of existing child constructors from the old constructor
// so the old child constructors can be garbage collected
delete existingConstructor._childConstructors;
} else {
base._childConstructors.push( constructor );
}
$.widget.bridge( name, constructor );
return constructor;
};
$.widget.extend = function( target ) {
var input = widget_slice.call( arguments, 1 ),
inputIndex = 0,
inputLength = input.length,
key,
value;
for ( ; inputIndex < inputLength; inputIndex++ ) {
for ( key in input[ inputIndex ] ) {
value = input[ inputIndex ][ key ];
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
// Clone objects
if ( $.isPlainObject( value ) ) {
target[ key ] = $.isPlainObject( target[ key ] ) ?
$.widget.extend( {}, target[ key ], value ) :
// Don't extend strings, arrays, etc. with objects
$.widget.extend( {}, value );
// Copy everything else by reference
} else {
target[ key ] = value;
}
}
}
}
return target;
};
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = widget_slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
instance = $.data( this, fullName );
if ( options === "instance" ) {
returnValue = instance;
return false;
}
if ( !instance ) {
return $.error( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" );
}
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
}
methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue.jquery ?
returnValue.pushStack( methodValue.get() ) :
methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, fullName );
if ( instance ) {
instance.option( options || {} );
if ( instance._init ) {
instance._init();
}
} else {
$.data( this, fullName, new object( options, this ) );
}
});
}
return returnValue;
};
};
$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
defaultElement: "<div>",
options: {
disabled: false,
// callbacks
create: null
},
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
this.uuid = widget_uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
this.bindings = $();
this.hoverable = $();
this.focusable = $();
if ( element !== this ) {
$.data( element, this.widgetFullName, this );
this._on( true, this.element, {
remove: function( event ) {
if ( event.target === element ) {
this.destroy();
}
}
});
this.document = $( element.style ?
// element within the document
element.ownerDocument :
// element is window or document
element.document || element );
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
}
this._create();
this._trigger( "create", null, this._getCreateEventData() );
this._init();
},
_getCreateOptions: $.noop,
_getCreateEventData: $.noop,
_create: $.noop,
_init: $.noop,
destroy: function() {
this._destroy();
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this.element
.unbind( this.eventNamespace )
.removeData( this.widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
.removeData( $.camelCase( this.widgetFullName ) );
this.widget()
.unbind( this.eventNamespace )
.removeAttr( "aria-disabled" )
.removeClass(
this.widgetFullName + "-disabled " +
"ui-state-disabled" );
// clean up events and states
this.bindings.unbind( this.eventNamespace );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
},
_destroy: $.noop,
widget: function() {
return this.element;
},
option: function( key, value ) {
var options = key,
parts,
curOption,
i;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
return $.widget.extend( {}, this.options );
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
parts = key.split( "." );
key = parts.shift();
if ( parts.length ) {
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
for ( i = 0; i < parts.length - 1; i++ ) {
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
curOption = curOption[ parts[ i ] ];
}
key = parts.pop();
if ( arguments.length === 1 ) {
return curOption[ key ] === undefined ? null : curOption[ key ];
}
curOption[ key ] = value;
} else {
if ( arguments.length === 1 ) {
return this.options[ key ] === undefined ? null : this.options[ key ];
}
options[ key ] = value;
}
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var key;
for ( key in options ) {
this._setOption( key, options[ key ] );
}
return this;
},
_setOption: function( key, value ) {
this.options[ key ] = value;
if ( key === "disabled" ) {
this.widget()
.toggleClass( this.widgetFullName + "-disabled", !!value );
// If the widget is becoming disabled, then nothing is interactive
if ( value ) {
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
}
}
return this;
},
enable: function() {
return this._setOptions({ disabled: false });
},
disable: function() {
return this._setOptions({ disabled: true });
},
_on: function( suppressDisabledCheck, element, handlers ) {
var delegateElement,
instance = this;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element;
element = suppressDisabledCheck;
suppressDisabledCheck = false;
}
// no element argument, shuffle and use this.element
if ( !handlers ) {
handlers = element;
element = this.element;
delegateElement = this.widget();
} else {
element = delegateElement = $( element );
this.bindings = this.bindings.add( element );
}
$.each( handlers, function( event, handler ) {
function handlerProxy() {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( !suppressDisabledCheck &&
( instance.options.disabled === true ||
$( this ).hasClass( "ui-state-disabled" ) ) ) {
return;
}
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy.guid = handler.guid =
handler.guid || handlerProxy.guid || $.guid++;
}
var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
eventName = match[1] + instance.eventNamespace,
selector = match[2];
if ( selector ) {
delegateElement.delegate( selector, eventName, handlerProxy );
} else {
element.bind( eventName, handlerProxy );
}
});
},
_off: function( element, eventName ) {
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
element.unbind( eventName ).undelegate( eventName );
},
_delay: function( handler, delay ) {
function handlerProxy() {
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
var instance = this;
return setTimeout( handlerProxy, delay || 0 );
},
_hoverable: function( element ) {
this.hoverable = this.hoverable.add( element );
this._on( element, {
mouseenter: function( event ) {
$( event.currentTarget ).addClass( "ui-state-hover" );
},
mouseleave: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-hover" );
}
});
},
_focusable: function( element ) {
this.focusable = this.focusable.add( element );
this._on( element, {
focusin: function( event ) {
$( event.currentTarget ).addClass( "ui-state-focus" );
},
focusout: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-focus" );
}
});
},
_trigger: function( type, event, data ) {
var prop, orig,
callback = this.options[ type ];
data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[ 0 ];
// copy original event properties over to the new event
orig = event.originalEvent;
if ( orig ) {
for ( prop in orig ) {
if ( !( prop in event ) ) {
event[ prop ] = orig[ prop ];
}
}
}
this.element.trigger( event, data );
return !( $.isFunction( callback ) &&
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
event.isDefaultPrevented() );
}
};
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
if ( typeof options === "string" ) {
options = { effect: options };
}
var hasOptions,
effectName = !options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options.effect || defaultEffect;
options = options || {};
if ( typeof options === "number" ) {
options = { duration: options };
}
hasOptions = !$.isEmptyObject( options );
options.complete = callback;
if ( options.delay ) {
element.delay( options.delay );
}
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
element[ method ]( options );
} else if ( effectName !== method && element[ effectName ] ) {
element[ effectName ]( options.duration, options.easing, callback );
} else {
element.queue(function( next ) {
$( this )[ method ]();
if ( callback ) {
callback.call( element[ 0 ] );
}
next();
});
}
};
});
var widget = $.widget;
}));

File diff suppressed because one or more lines are too long

@ -0,0 +1,49 @@
{% load static %}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="description" content="基于Django的视频点播网站">
<meta name="google-site-verification" content="UhrjUG70pwiY6Sp4u4p5Q3ol8iD7yx4ZSwmGBpoaWik" />
<link rel="icon" href="{% static 'img/favicon.ico' %}" type="image/x-icon">
<title>视点</title>
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" type="text/css" href="{% static 'css/semantic.custom.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
{% block css %}{% endblock css %}
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?e3be3decd24d944e0bc592558617d210";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
{% include "base/header.html" %}
<div class="ui container" id="v-content">
{% block content %}
{% endblock content %}
</div>
{% include "base/footer.html" %}
<script src="https://cdn.staticfile.org/jquery/1.9.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
<script>
var search_url = "{% url 'video:search' %}"
</script>
<script src="{% static 'js/header.js' %}"></script>
{% block javascript %}
{% endblock javascript %}
{% block modal %}
{% endblock modal %}
</body>
</html>

@ -0,0 +1,13 @@
{% load static %}
<div class="ui vertical footer segment ">
<div class="ui center aligned container">
<div class="ui divider"></div>
<img src="{% static 'img/logo.png' %}" class="ui centered mini image">
<div class="ui horizontal small divided link list">
<a class="item" href="#">北京堆栈科技有限公司</a>
<a class="item" href="#">联系我们</a>
<a class="item" href="#">京ICP证090287</a>
</div>
</div>
</div>

@ -0,0 +1,17 @@
{% if form.errors %}
<div class="ui info message">
<ul class="list">
{% for field in form %}
{% for error in field.errors %}
<li class="item">{{ error|escape }}</li>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<li class="item">{{ error|escape }}</li>
{% endfor %}
</ul>
</div>
{% endif %}

@ -0,0 +1,7 @@
{% if messages %}
<div class="ui info message">
{% for message in messages %}
<div class="item">{{ message }}</div>
{% endfor %}
</div>
{% endif %}

@ -0,0 +1,52 @@
{% load static %}
{% load thumbnail %}
<div class="ui sticky fixed menu">
<div class="ui container">
<a href="/" class="borderless header item">
<img class="logo" src="{% static 'img/logo.png' %}">
视点
</a>
<div class="v-header-extra">
<div class="ui small icon input v-video-search">
<input class="prompt" value="{{ q }}" type="text" placeholder="搜索视频" id="v-search">
<i id="search" class="search icon" style="cursor:pointer;"></i>
</div>
{% if user.is_authenticated %}
<div class="ui inline dropdown" id="v-header-avatar" style="">
<div class="" style="display:inline-block;font-weight:bold;">
{% thumbnail user.avatar "200x200" crop="center" as im %}
<img class="ui avatar image" src="{{ im.url }}">
{% empty %}
<img class="ui avatar image" src="{% static 'img/img_default_avatar.png' %}">
{% endthumbnail %}
{{ user.username }}
</div>
<i class="dropdown icon"></i>
<div class="menu">
<div class="item" onclick="window.location='{% url 'users:profile' user.pk %}';">
<i class="user icon"></i>
<span>个人资料</span>
</div>
<div class="item" onclick="window.location='{% url 'users:collect_videos' user.pk %}';">
<i class="bookmark icon"></i>
<span>我的收藏</span>
</div>
<div class="item" onclick="window.location='{% url 'users:like_videos' user.pk %}';">
<i class="heart icon"></i>
<span>我的喜欢</span>
</div>
<div class="item" onclick="window.location='{% url 'users:logout' %}';">
<i class="sign-out icon"></i>
<span>退出</span>
</div>
</div>
</div>
{% else %}
<a class="ui tiny secondary basic button" id="v-header-login" href="{% url 'users:login' %}?next={{ request.path }}">登录</a>
{% endif %}
</div>
</div>
</div>

@ -0,0 +1,18 @@
<div class="ui secondary vertical pointing menu">
<a id="id_profile" class="link item" href="{% url 'users:profile' user.pk %}">
<i class="grey user icon"></i>
<span class="v-nav-title">个人资料</span>
</a>
<a id="id_password" class="link item" href="{% url 'users:change_password' %}">
<i class="grey edit icon"></i>
<span class="v-nav-title">修改密码</span>
</a>
<a id="id_subscribe" class="link item" href="{% url 'users:subscribe' user.pk %}">
<i class="grey rss icon"></i>
<span class="v-nav-title">订阅设置</span>
</a>
<a id="id_feedback" class="link item" href="{% url 'users:feedback' %}">
<i class="grey list icon"></i>
<span class="v-nav-title">反馈与建议</span>
</a>
</div>

@ -0,0 +1,19 @@
{% if is_paginated %}
<div class="video-page">
<div class="ui circular labels">
{% if page_obj.has_previous %}
<a class="ui circular label" href="?page={{ page_obj.previous_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">&lt;</a>
{% endif %}
{% for i in page_list %}
{% if page_obj.number == i %}
<a class="ui red circular label">{{ i }}</a>
{% else %}
<a class="ui circular label" href="?page={{ i }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="ui circular label" href="?page={{ page_obj.next_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">&gt;</a>
{% endif %}
</div>
</div>
{% endif %}

@ -0,0 +1,28 @@
{% load video_tag %}
{% load static %}
{% load thumbnail %}
{% for item in comments %}
<div class="comment">
<a class="avatar">
{% thumbnail item.avatar "200x200" crop="center" as im %}
<img class="ui avatar image" src="{{ im.url }}">
{% empty %}
<img class="ui avatar image" src="{% static 'img/img_default_avatar.png' %}">
{% endthumbnail %}
</a>
<div class="content">
<a class="author">
{% if item.nickname %} {{item.nickname}} {% else %} 匿名 {% endif %}
</a>
<div class="metadata">
<span class="date">{{ item.timestamp|time_since }}</span>
</div>
<div class="text">
{{ item.content }}
</div>
</div>
</div>
{% endfor %}

@ -0,0 +1,62 @@
{% load static %}
<html>
<head>
<meta charset="utf-8"/>
<link rel="icon" href="{% static 'img/favicon.ico' %}" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" type="text/css" href="{% static 'css/semantic.custom.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/admin.css' %}"/>
<title>后台管理</title>
</head>
<body>
<div class="ui inverted huge borderless fixed fluid menu">
<a class="header item" href="{% url 'myadmin:index' %}">
<img src="{% static 'img/logo.png' %}" id="logo">后台管理
</a>
<div class="right menu">
<a class="item" href="{% url 'home' %}" target="_blank">前台首页</a>
<a class="item" href="{% url 'myadmin:index' %}">后台首页</a>
<a class="item">{{ user }}</a>
<a class="item" href="{% url 'myadmin:logout' %}">退出</a>
</div>
</div>
<div class="ui grid">
<div class="row">
<div class="column" id="sidebar">
<div class="ui secondary vertical fluid menu">
<a class="item" href="{% url 'myadmin:index' %}" id="index">总览</a>
<div class="ui divider"></div>
<a class="item" href="{% url 'myadmin:video_list' %}" id="video_list">视频列表</a>
<a class="item" href="{% url 'myadmin:video_add' %}" id="video_add">添加视频</a>
<div class="ui divider"></div>
<a class="item" href="{% url 'myadmin:classification_list' %}" id="classification_list">分类列表</a>
<a class="item" href="{% url 'myadmin:classification_add' %}" id="classification_add">添加分类</a>
<div class="ui divider"></div>
<a class="item" href="{% url 'myadmin:user_list' %}" id="user_list">用户列表</a>
<a class="item" href="{% url 'myadmin:user_add' %}" id="user_add">添加用户</a>
<div class="ui divider"></div>
<a class="item" href="{% url 'myadmin:comment_list' %}" id="comment_list">评论列表</a>
<div class="ui divider"></div>
<a class="item" href="{% url 'myadmin:subscribe' %}" id="subscribe">订阅通知</a>
<a class="item" href="{% url 'myadmin:feedback_list' %}" id="feedback_list">用户反馈</a>
</div>
</div>
</div>
<div class="column" id="content">
{% block content %}
{% endblock content %}
</div>
</div>
</div>
{% block modal %}
{% endblock modal %}
<script src="https://cdn.staticfile.org/jquery/1.9.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="{% static 'js/myadmin/admin_nav.js' %}"></script>
{% block javascript %}
{% endblock javascript %}
</body>
</html>

@ -0,0 +1,43 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>分类添加</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:classification_add' %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<div class="field">
<label>分类名称</label>
{{ form.title }}
</div>
<div class="field">
<div class="ui checkbox">
{{form.status}}
<label>是否启用</label>
</div>
</div>
<button class="ui primary button" type="submit">添加</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,23 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>分类添加</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<h3>添加成功!</h3>
<h3><a href="{% url 'myadmin:classification_list' %}">&lt;&lt;返回列表</a></h3>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,48 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>分类编辑</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:classification_edit' form.instance.pk %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<div class="field">
<label>分类名称</label>
{{ form.title }}
</div>
<div class="field">
<div class="ui checkbox">
{{form.status}}
<label>是否启用</label>
</div>
</div>
<button class="ui primary button" type="submit">保存</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}
{% block modal %}
{% endblock modal %}

@ -0,0 +1,63 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3 class="ui header six wide column">分类列表</h3>
<div class="v-title-extra ten wide column">
<div class="ui action input v-admin-search">
<input type="text" placeholder="Search..." value="{{q}}" id="v-search">
<button class="ui small button" id="search">搜索</button>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="row">
<table class="ui unstackable single line striped selectable table">
<thead>
<tr><th>#id</th><th>名称</th></th><th>状态</th><th>操作</th></tr>
</thead>
<tbody class="video-list">
{% for item in classification_list %}
<tr classification-id="{{item.id}}">
<td> {{item.id}}</td>
<td> {{item.title}}</td>
<td> {% if item.status %}启用{% else %}未启用{% endif %}</td>
<td>
<a class="ui primary button classification-edit" href="{% url 'myadmin:classification_edit' item.id %}">编辑</a>
<a class="ui button classification-delete">删除</a>
</td>
</tr>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="6">
{% include 'myadmin/page_nav.html' %}
</th>
</tr>
</tfoot>
</table>
</div>
</div>
{% endblock content %}
{% block modal %}
{% include "myadmin/classification_list_modal.html" %}
{% endblock modal %}
{% block javascript %}
<script>
var search_url = "{% url 'myadmin:classification_list' %}"
var api_classification_delete = "{% url 'myadmin:classification_delete' %}"
</script>
<script src="{% static 'js/myadmin/classification_list.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,15 @@
<!--- 删除模态框 -->
<div class="ui tiny modal delete">
<div class="header">
删除确认
</div>
<div class="image content">
<div class="description">
确认删除分类(该分类下面的视频将会被删除)
</div>
</div>
<div class="actions">
<div class="ui primary approve button">确认</div>
<div class="ui deny button">取消</div>
</div>
</div>

@ -0,0 +1,63 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3 class="ui header six wide column">评论列表</h3>
<div class="v-title-extra ten wide column">
<div class="ui action input v-admin-search">
<input type="text" placeholder="Search..." value="{{q}}" id="v-search">
<button class="ui small button" id="search">搜索</button>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="row">
<table class="ui unstackable single line striped selectable table">
<thead>
<tr><th>用户</th><th>昵称</th><th>评论内容</th></th><th>时间</th><th>操作</th></tr>
</thead>
<tbody class="video-list">
{% for item in comment_list %}
<tr comment-id="{{item.id}}">
<td> {{item.user_id}}</td>
<td> {{item.nickname}}</td>
<td> {{item.content}}</td>
<td> {{item.timestamp|date:'Y-m-d H:i'}}</td>
<td>
<a class="ui button comment-delete">删除</a>
</td>
</tr>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="6">
{% include 'myadmin/page_nav.html' %}
</th>
</tr>
</tfoot>
</table>
</div>
</div>
{% endblock content %}
{% block modal %}
{% include "myadmin/comment_list_modal.html" %}
{% endblock modal %}
{% block javascript %}
<script>
var search_url = "{% url 'myadmin:comment_list' %}"
var api_comment_delete = "{% url 'myadmin:comment_delete' %}"
</script>
<script src="{% static 'js/myadmin/comment_list.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,15 @@
<!--- 删除模态框 -->
<div class="ui tiny modal delete">
<div class="header">
删除评论
</div>
<div class="image content">
<div class="description">
确认删除该条评论?
</div>
</div>
<div class="actions">
<div class="ui primary approve button">确认</div>
<div class="ui deny button">取消</div>
</div>
</div>

@ -0,0 +1,62 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3 class="ui header six wide column">反馈列表</h3>
<div class="v-title-extra ten wide column">
<div class="ui action input v-admin-search">
<input type="text" placeholder="Search..." value="{{q}}" id="v-search">
<button class="ui small button" id="search">搜索</button>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="row">
<table class="ui unstackable single line striped selectable table">
<thead>
<tr><th>内容</th><th>联系方式</th><th>时间</th><th>操作</th></tr>
</thead>
<tbody class="video-list">
{% for item in feedback_list %}
<tr feedback-id="{{item.id}}">
<td> {{item.content}}</td>
<td> {{item.contact}}</td>
<td> {{item.timestamp|date:'Y-m-d H:i'}}</td>
<td>
<a class="ui button feedback-delete">删除</a>
</td>
</tr>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="6">
{% include 'myadmin/page_nav.html' %}
</th>
</tr>
</tfoot>
</table>
</div>
</div>
{% endblock content %}
{% block modal %}
{% include "myadmin/feedback_list_modal.html" %}
{% endblock modal %}
{% block javascript %}
<script>
var search_url = "{% url 'myadmin:feedback_list' %}"
var api_feedback_delete = "{% url 'myadmin:feedback_delete' %}"
</script>
<script src="{% static 'js/myadmin/feedback_list.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,15 @@
<!--- 删除模态框 -->
<div class="ui tiny modal delete">
<div class="header">
确认删除
</div>
<div class="image content">
<div class="description">
确认删除该条内容?
</div>
</div>
<div class="actions">
<div class="ui primary approve button">确认</div>
<div class="ui deny button">取消</div>
</div>
</div>

@ -0,0 +1,84 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>总览</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui tiny statistics">
<div class="statistic">
<div class="value">{{ video_count }}</div>
<div class="label">视频数</div>
</div>
<div class="statistic">
<div class="value">{{ video_has_published_count }}</div>
<div class="label">发布中</div>
</div>
<div class="statistic">
<div class="value">{{ video_not_published_count }}</div>
<div class="label">未发布</div>
</div>
</div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui tiny statistics">
<div class="statistic">
<div class="value">{{ user_count }}</div>
<div class="label"> 用户数</div>
</div>
<div class="statistic">
<div class="value">{{ user_today_count }}</div>
<div class="label">今日新增</div>
</div>
</div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui tiny statistics">
<div class="statistic">
<div class="value">{{ comment_count }}</div>
<div class="label">评论数</div>
</div>
<div class="statistic">
<div class="value">{{ comment_today_count }}</div>
<div class="label">今日新增</div>
</div>
</div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<h4>
{% if not user.is_superuser %}
{{user}}权限:查看数据、搜索数据。暂无编辑和删除数据权限。
<br/>下载代码后,可自行添加超级用户,即可使用全部功能权限。
<br/>添加方法python3 manage.py createsuperuser
<br/>如有问题可咨询开发者weixin id: java2048
{% endif %}
</h4>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,47 @@
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>登录</title>
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" type="text/css" href="{% static 'css/semantic.custom.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
</head>
<body id="v-account-body">
<div class="ui middle aligned center aligned grid">
<div class="column v-account">
<h2 class="ui teal image header">
<img src="{% static 'img/logo.png' %}" class="image">
<div class="content">
后台管理系统
</div>
</h2>
<form class="ui large form" novalidate method="post" action="{% url 'myadmin:login' %}"
enctype="multipart/form-data">
{% csrf_token %}
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
{{form.username}}
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
{{form.password}}
</div>
</div>
<input type="hidden" name="next" value="{{ next }}"/>
<button class="ui fluid large teal submit button" type="submit">登录</button>
</div>
{% include "base/form_errors.html" %}
</form>
</div>
</div>
</body>
</html>

@ -0,0 +1,23 @@
{% load static %}
{% load thumbnail %}
{% load video_tag %}
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
</head>
<body>
<h3>{{ video.title }}</h3>
<p>
<a class="image" href="{{site_url}}{% url 'video:detail' video.id %}">
<img class="ui image"
style="object-fit: cover; object-position: center;height: 200px;width: 300px;"
src="{{site_url}}{{video.cover}}">
</a>
</p>
<p>{{video.desc}}</p>
<a class="ui button" href="{{site_url}}{% url 'video:detail' video.id %}">点击进入</a>
</body>
</html>

@ -0,0 +1,20 @@
{% if is_paginated %}
<div class="video-page">
<div class="ui borderless pagination menu">
{% if page_obj.has_previous %}
<a class="item" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}"><i
class="left arrow icon"></i></a></a>
{% endif %}
{% for i in page_list %}
{% if page_obj.number == i %}
<a class="active item">{{ i }}</a>
{% else %}
<a class="item" href="?page={{ i }}{% if q %}&q={{q}}{% endif %}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="item" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}"><i class="icon right arrow"></i></a>
{% endif %}
</div>
</div>
{% endif %}

@ -0,0 +1,39 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>网站设置</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:setting' 1 %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<div class="field">
<div class="ui checkbox">
{{form.switch_mail}}
<label>开启邮件服务</label>
</div>
</div>
<button class="ui primary button" type="submit">保存</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,58 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>订阅通知</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<div class="ui small input focus">
<!--<input type="text" id="video_id" required placeholder="请输入视频id">-->
<form class="ui form" novalidate method="post" action=""
enctype="multipart/form-data" role="form">
<div class="field">
<label>请选择要推送的视频</label>
<div class="ui selection dropdown">
<i class="dropdown icon"></i>
<div class="default text">请选择</div>
<div class="menu">
{% for item in video_list %}
<div class="item" data-value="{{item.id}}">{{item.title}}</div>
{% endfor %}
</div>
</div>
</div>
<h4>
<div class="ui tiny primary button" id="send-mail" video-id="{{video_id}}">通知订阅用户</div>
</h4>
</form>
</div>
<div id="send-mail-progress"></div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<script>
var api_send_mail = "{% url 'myadmin:subscribe' %}";
$(function(){
$('.ui .dropdown').dropdown();
});
</script>
<script src="{% static 'js/myadmin/send_mail.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,47 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>用户添加</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:user_add' %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<div class="field">
<label>用户名</label>
{{ form.username }}
</div>
<div class="field">
<label>密码</label>
{{ form.password }}
</div>
<div class="field">
<div class="ui checkbox">
{{form.is_staff}}
<label>管理员账号</label>
</div>
</div>
<button class="ui primary button" type="submit">添加</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,23 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>用户添加</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<h3>添加成功!</h3>
<h3><a href="{% url 'myadmin:user_list' %}">&lt;&lt;返回列表</a></h3>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,47 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>用户编辑</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:user_edit' form.instance.pk %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<div class="field">
<label>用户名</label>
{{ form.username }}
</div>
<div class="field">
<div class="ui checkbox">
{{form.is_staff}}
<label>管理员账号</label>
</div>
</div>
<button class="ui primary button" type="submit">保存</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}
{% block modal %}
{% endblock modal %}

@ -0,0 +1,65 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3 class="ui header six wide column">用户列表</h3>
<div class="v-title-extra ten wide column">
<div class="ui action input v-admin-search">
<input type="text" placeholder="Search..." value="{{q}}" id="v-search">
<button class="ui small button" id="search">搜索</button>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="row">
<table class="ui unstackable single line striped selectable table">
<thead>
<tr><th>用户名</th><th>账号类型</th><th>加入时间</th><th>操作</th></tr>
</thead>
<tbody class="video-list">
{% for item in user_list %}
<tr user-id="{{item.id}}">
<td> {{item.username}}</td>
<td> {% if item.is_staff %}管理员{% else %}普通{% endif %}</td>
<td> {{item.date_joined|date:'Y-m-d H:i'}}</td>
<td>
<a class="ui primary button user-edit" href="{% url 'myadmin:user_edit' item.id %}">编辑</a>
<a class="ui button user-delete">删除</a>
</td>
</tr>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="6">
{% include 'myadmin/page_nav.html' %}
</th>
</tr>
</tfoot>
</table>
</div>
</div>
{% endblock content %}
{% block modal %}
{% include "myadmin/user_list_modal.html" %}
{% endblock modal %}
{% block javascript %}
<script>
var api_user_delete = "{% url 'myadmin:user_delete' %}";
var search_url = "{% url 'myadmin:user_list' %}"
</script>
<script src="{% static 'js/myadmin/user_list.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,15 @@
<!--- 删除模态框 -->
<div class="ui tiny modal delete">
<div class="header">
删除用户
</div>
<div class="image content">
<div class="description">
确认删除该用户?
</div>
</div>
<div class="actions">
<div class="ui primary approve button">确认</div>
<div class="ui deny button">取消</div>
</div>
</div>

@ -0,0 +1,60 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>视频添加</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
{% csrf_token %}
<label id="progress_label" class="ui large blue label" for="chunked_upload">
<i class="upload icon"></i> 上传视频
<input id="chunked_upload" type="file" name="the_file" hidden>
</label>
<div id="progress_layout" class="n">
<div class="ui progress" data-percent="74" id="upload_progress">
<div class="bar">
<div class="progress"></div>
</div>
<div class="label" id="upload_label">正在上传...</div>
</div>
</div>
<div id="next_layout" class="n">
<button id="next" class="ui primary right labeled icon button">
<i class="right arrow icon"></i>
下一步
</button>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block modal %}
{% endblock modal %}
{% block javascript %}
<script>
var api_chunked_uplad = "{% url 'myadmin:api_chunked_upload' %}";
var api_chunked_upload_complete = "{% url 'myadmin:api_chunked_upload_complete' %}";
</script>
<script src="{% static 'js/upload/jquery.js' %}"></script>
<script src="{% static 'js/upload/jquery.ui.widget.js' %}"></script>
<script src="{% static 'js/upload/jquery.iframe-transport.js' %}"></script>
<script src="{% static 'js/upload/jquery.fileupload.js' %}"></script>
<script src="{% static 'js/upload/spark-md5.js' %}"></script>
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="{% static 'js/myadmin/video_upload.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,101 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>视频编辑</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:video_edit' form.instance.pk %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<input name="video_id" id="video_id" type="hidden">
<div class="field">
<label>视频标题</label>
{{ form.title }}
</div>
<div class="field">
<label>视频描述</label>
{{ form.desc }}
</div>
<div class="field">
<label>视频分类</label>
<div class="ui selection dropdown">
{{ form.classification }}
<i class="dropdown icon"></i>
<div class="default text">请选择</div>
<div class="menu">
{% for item in clf_list %}
<div class="item" data-value="{{item.id}}">{{item.title}}</div>
{% endfor %}
</div>
</div>
</div>
<div class="field">
<label>发布状态</label>
<div class="ui selection dropdown">
{{form.status}}
<i class="dropdown icon"></i>
<div class="default text">请选择</div>
<div class="menu">
<div class="item" data-value="0">发布</div>
<div class="item" data-value="1">取消发布</div>
</div>
</div>
</div>
<div class="field">
<label>视频封面</label>
{% thumbnail video.cover "300x200" crop="center" as im %}
<img class="ui small image" src="{{ im.url }}">
{% empty %}
{% endthumbnail %}
<div class="v-form-field">
<label class="ui large green label" for="id_cover">
<i class="upload icon"></i> 上传封面
{{form.cover}}
</label>
<span id="file_is_choose" class="n">文件已选择</span>
</div>
</div>
<button class="ui primary button" type="submit">保存</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}
{% block modal %}
{% endblock modal %}
{% block javascript %}
<script>
$(function(){
$('.ui .dropdown').dropdown();
$("#id_cover").change(function(){
$("#file_is_choose").show()
});
});
</script>
{% endblock javascript %}

@ -0,0 +1,68 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3 class="ui header six wide column">视频列表</h3>
<div class="v-title-extra ten wide column">
<div class="ui action input v-admin-search">
<input type="text" placeholder="Search..." value="{{q}}" id="v-search">
<button class="ui small button" id="search">搜索</button>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="row">
<table class="ui unstackable single line striped selectable table">
<thead>
<tr><th>#id</th><th>标题</th><th>分类</th><th>状态</th><th>访问次数</th><th>创建时间</th><th>操作</th></tr>
</thead>
<tbody class="video-list">
{% for item in video_list %}
<tr video-id="{{item.id}}">
<td> {{item.id}}</td>
<td> {{item.title|default:""}}</td>
<td> {{item.classification|default:""}}</td>
<td> {{item.get_status_display}}</td>
<td> {{item.view_count}}</td>
<td> {{item.create_time|date:'Y-m-d H:i'}}</td>
<td>
<a class="ui primary button video-edit" href="{% url 'myadmin:video_edit' item.id %}">编辑</a>
<a class="ui button video-delete">删除</a>
</td>
</tr>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th colspan="6">
{% include 'myadmin/page_nav.html' %}
</th>
</tr>
</tfoot>
</table>
</div>
</div>
{% endblock content %}
{% block modal %}
{% include "myadmin/video_list_modal.html" %}
{% endblock modal %}
{% block javascript %}
<script>
var api_video_delete = "{% url 'myadmin:video_delete' %}";
var search_url = "{% url 'myadmin:video_list' %}"
</script>
<script src="{% static 'js/myadmin/video_list.js' %}"></script>
{% endblock javascript %}

@ -0,0 +1,15 @@
<!--- 删除模态框 -->
<div class="ui tiny modal delete">
<div class="header">
删除视频
</div>
<div class="image content">
<div class="description">
确认删除该条视频?
</div>
</div>
<div class="actions">
<div class="ui primary approve button" href="{{ request.path }}">确认</div>
<div class="ui deny button">取消</div>
</div>
</div>

@ -0,0 +1,93 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>视频资料</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<form class="ui form" novalidate method="post" action="{% url 'myadmin:video_publish' form.instance.pk %}"
enctype="multipart/form-data" role="form">
{% csrf_token %}
<input name="video_id" id="video_id" type="hidden">
<div class="field">
<label>视频标题</label>
{{ form.title }}
</div>
<div class="field">
<label>视频描述</label>
{{ form.desc }}
</div>
<div class="field">
<label>视频分类</label>
<div class="ui selection dropdown">
{{ form.classification }}
<i class="dropdown icon"></i>
<div class="default text">请选择</div>
<div class="menu">
{% for item in clf_list %}
<div class="item" data-value="{{item.id}}">{{item.title}}</div>
{% endfor %}
</div>
</div>
</div>
{{form.status}}
<div class="field">
<label>视频封面</label>
{% if video.cover %}
{% thumbnail video.cover "300x200" crop="center" as im %}
<img class="ui small image" src="{{ im.url }}">
{% empty %}
{% endthumbnail %}
{% endif %}
<div class="v-form-field">
<label class="ui large green label" for="id_cover">
<i class="upload icon"></i> 上传封面
{{form.cover}}
</label>
<span id="file_is_choose" class="n">文件已选择</span>
</div>
</div>
<button class="ui primary button" type="submit">发布</button>
{% include "base/form_errors.html" %}
{% include "base/form_messages.html" %}
</form>
</div>
</div>
</div>
{% endblock content %}
{% block modal %}
{% endblock modal %}
{% block javascript %}
<script>
$(function(){
$('.ui .dropdown').dropdown();
$("#id_cover").change(function(){
$("#file_is_choose").show()
});
});
</script>
{% endblock javascript %}

@ -0,0 +1,23 @@
{% extends 'myadmin/base.html' %}
{% load static %}
{% load thumbnail %}
{% block content %}
<div class="ui grid">
<div class="row">
<h3>视频资料</h3>
</div>
<div class="ui divider"></div>
<div class="row">
<div class="v-form-wrap">
<h3>发布成功!</h3>
<h3><a href="{% url 'myadmin:video_list' %}">&lt;&lt;返回列表</a></h3>
</div>
</div>
</div>
{% endblock content %}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save