Advertisement
Guest User

Untitled

a guest
Jun 7th, 2018
425
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 63.03 KB | None | 0 0
  1. import os
  2.  
  3. # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
  4. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  5.  
  6.  
  7. # Quick-start development settings - unsuitable for production
  8. # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
  9.  
  10. # SECURITY WARNING: keep the secret key used in production secret!
  11. SECRET_KEY = 'o0-0sp)e7qbr*6(v((xbv0q89&p#g=312_q=q+2uq4%ros(2+0'
  12.  
  13. # SECURITY WARNING: don't run with debug turned on in production!
  14. DEBUG = True
  15.  
  16. ALLOWED_HOSTS = []
  17.  
  18. EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
  19.  
  20. # Application definition
  21.  
  22. INSTALLED_APPS = [
  23. 'django.contrib.admin',
  24. 'django.contrib.auth',
  25. 'django.contrib.contenttypes',
  26. 'django.contrib.sessions',
  27. 'django.contrib.messages',
  28. 'django.contrib.staticfiles',
  29. 'drivers', # my apps
  30. ]
  31.  
  32. MIDDLEWARE = [
  33. 'django.middleware.security.SecurityMiddleware',
  34. 'django.contrib.sessions.middleware.SessionMiddleware',
  35. 'django.middleware.common.CommonMiddleware',
  36. 'django.middleware.csrf.CsrfViewMiddleware',
  37. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  38. 'django.contrib.messages.middleware.MessageMiddleware',
  39. 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  40. ]
  41.  
  42. ROOT_URLCONF = 'buster.urls'
  43.  
  44. TEMPLATES = [
  45. {
  46. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  47. 'DIRS': [os.path.join(BASE_DIR, 'templates')],
  48. 'APP_DIRS': True,
  49. 'OPTIONS': {
  50. 'context_processors': [
  51. 'django.template.context_processors.debug',
  52. 'django.template.context_processors.request',
  53. 'django.contrib.auth.context_processors.auth',
  54. 'django.contrib.messages.context_processors.messages',
  55. ],
  56. },
  57. },
  58. ]
  59.  
  60. WSGI_APPLICATION = 'buster.wsgi.application'
  61.  
  62.  
  63. # Database
  64. # https://docs.djangoproject.com/en/2.0/ref/settings/#databases
  65.  
  66. DATABASES = {
  67. 'default': {
  68. 'ENGINE': 'django.db.backends.postgresql_psycopg2',
  69. 'NAME': 'buster_db',
  70. 'USER': 'postgres',
  71. 'PASSWORD': 'qwerty',
  72. 'HOST': 'localhost',
  73. 'PORT': '5432',
  74. }
  75. }
  76.  
  77.  
  78. # Password validation
  79. # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
  80.  
  81. AUTH_PASSWORD_VALIDATORS = [
  82. {
  83. 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
  84. },
  85. {
  86. 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
  87. },
  88. {
  89. 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
  90. },
  91. {
  92. 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
  93. },
  94. ]
  95.  
  96.  
  97. # Internationalization
  98. # https://docs.djangoproject.com/en/2.0/topics/i18n/
  99.  
  100. LANGUAGE_CODE = 'ru-ru'
  101.  
  102. TIME_ZONE = 'UTC'
  103.  
  104. USE_I18N = True
  105.  
  106. USE_L10N = True
  107.  
  108. USE_TZ = True
  109.  
  110.  
  111. # Static files (CSS, JavaScript, Images)
  112. # https://docs.djangoproject.com/en/2.0/howto/static-files/
  113.  
  114. STATIC_URL = '/static/'
  115. STATICFILES_DIRS = [
  116. os.path.join(BASE_DIR, 'static'),
  117. ]
  118. from django.contrib import admin
  119. from django.urls import path, include
  120.  
  121. urlpatterns = [
  122. path('admin/', admin.site.urls),
  123. path('accounts/', include('drivers.urls')), # application url's
  124. ]
  125. from django.forms import ModelForm
  126. from django.core.exceptions import ValidationError
  127. from .models import Organization, Car, Profile, OrganizationRoute, Schedule
  128. from django.contrib.auth.models import User
  129. from django.contrib.auth.forms import UserChangeForm
  130.  
  131. import requests, json
  132.  
  133. # Your forms here.
  134. class OrganizationForm(ModelForm):
  135.  
  136. def clean_unp(self):
  137. EGR_API = 'http://egr.gov.by/egrn/API.jsp?NM='
  138. unp = self.cleaned_data['unp']
  139. try:
  140. r = requests.get(EGR_API + unp)
  141. json_data = r.json()
  142. except json.decoder.JSONDecodeError:
  143. raise ValidationError('UNP is not register')
  144.  
  145. if json_data[0]['ACT'] is False:
  146. raise ValidationError('UNP number in not active')
  147.  
  148. return unp
  149.  
  150. class Meta:
  151. model = Organization
  152. fields = ('name', 'unp', )
  153.  
  154.  
  155. class ProfileForm(UserChangeForm):
  156. class Meta:
  157. model = User
  158. fields = ('email', 'username', 'first_name', 'last_name', 'password')
  159.  
  160. class OrganizationRoutesForm(ModelForm):
  161. class Meta:
  162. model = OrganizationRoute
  163. fields = ('start_place', 'final_place', 'start_time', 'final_time', 'price')
  164. exclude = ('organization', )
  165.  
  166. class OrganizationCarsForm(ModelForm):
  167. class Meta:
  168. model = Car
  169. fields = ('model', 'seats', 'number')
  170. labels = {'model': 'Car model',
  171. 'seats': 'Number of seats',
  172. 'number': 'Car number',
  173. }
  174. exclude = ('organization', )
  175.  
  176. class ScheduleForm(ModelForm):
  177. class Meta:
  178. model = Schedule
  179. fields = ('date', 'org_route', 'driver', 'car')
  180. from django.db import models
  181. from django.contrib.auth.models import User
  182.  
  183. # Create your models here.
  184.  
  185. class Profile(models.Model):
  186. mobile_number = models.CharField(max_length=12, unique=True)
  187. user = models.OneToOneField(User, on_delete=models.CASCADE)
  188.  
  189. class Organization(models.Model):
  190. name = models.CharField(max_length=255)
  191. unp = models.CharField(max_length=9, unique=True)
  192.  
  193. class Car(models.Model):
  194. model = models.CharField(max_length=255)
  195. seats = models.IntegerField()
  196. number = models.CharField(max_length=9, unique=True)
  197. organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
  198.  
  199. class Employee(models.Model):
  200. STATUS = (
  201. ('OP', 'Operator'),
  202. ('DR', 'Driver')
  203. )
  204. name = models.OneToOneField(User, on_delete=models.CASCADE)
  205. status = models.CharField(max_length=8, choices=STATUS)
  206. hire_date = models.DateField()
  207. hire_end_date = models.DateField(null=True)
  208. organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
  209.  
  210. class OrganizationInvite(models.Model):
  211. code = models.CharField(unique=True, max_length=10)
  212. organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
  213.  
  214. class OrganizationRoute(models.Model):
  215. start_place = models.CharField(max_length=255)
  216. final_place = models.CharField(max_length=255)
  217. #route_distance = models.IntegerField() # distance rout value
  218. start_time = models.TimeField()
  219. #route_time = models.CharField(max_length=4) # time in rout distance
  220. final_time = models.TimeField()
  221. price = models.IntegerField()
  222. organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
  223.  
  224. class Schedule(models.Model):
  225. date = models.DateField()
  226. org_route = models.ForeignKey(OrganizationRoute, on_delete=models.CASCADE)
  227. driver = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='schedule_driver')
  228. car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name='schedule_car')
  229.  
  230. class Flight(models.Model):
  231. STATUS = (
  232. ('True', 'True'),
  233. ('False', 'False')
  234. )
  235. schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE, related_name='flight_schedule')
  236. user = models.OneToOneField(User, on_delete=models.CASCADE)
  237. status = models.CharField(max_length=5, choices=STATUS)
  238.  
  239. from django.conf.urls import url, include, re_path
  240. from .views import signup, new_organization, send_invite_code, get_employees, edit_profile, add_route, add_car, add_schedule, get_cars, get_routes, get_main, get_search, get_user_routes, join_invite, flight_activate, get_stats
  241. from django.contrib.auth.views import login, logout
  242.  
  243. urlpatterns = [
  244. # url(r'^profile/$', home),
  245. url(r'^signup/$', signup, name='signup'),
  246. url(r'^logout/', logout, name='logout'),
  247. url(r'^login/$', login, {'template_name': 'accounts/login.html'}, name='login'),
  248. url(r'^setting/$', edit_profile, name='settings_page'),
  249. # url(r'^organization/$', get_organization, name='organization'),
  250. url(r'^organization/(?P<invite>[a-zA-Z0-9]+)$', join_invite, name='invite_join'),
  251. #url(r'^organization/new/$', new_organization, name='new_org'),
  252. url(r'^organization/sho/$', send_invite_code, name='invite'),
  253.  
  254. url(r'^organization/activate/$', flight_activate, name='flight_activate'),
  255.  
  256. # organization pages
  257. url(r'^main/$', get_main, name='main_page'),
  258. url(r'^search$', get_search, name='search_page'),
  259. url(r'^routes', get_user_routes, name='user_routes'),
  260. # add
  261. url(r'^organization/add/route$', add_route, name='add_route'),
  262. url(r'^organization/add/car$', add_car, name='add_car'),
  263. url(r'^organization/add/schedule$', add_schedule, name='add_schedule'),
  264. # get
  265. url(r'^organization/employees/$', get_employees, name='employees'),
  266. url(r'^organization/cars/$', get_cars, name='cars'),
  267. url(r'^organization/routes/$', get_routes, name='routes'),
  268. url(r'^organization/stats/$', get_stats, name='stats'),
  269. from django.shortcuts import render, redirect
  270. from django.db.models import Q
  271. from django.contrib.auth.forms import UserCreationForm, User
  272. from .forms import OrganizationForm, ProfileForm, OrganizationRoutesForm, OrganizationCarsForm, ScheduleForm
  273. from django.contrib.auth import authenticate, login, logout
  274. from .models import Profile, Organization, Schedule, Car, Employee, OrganizationInvite, OrganizationRoute, Flight
  275. from django.core.mail import send_mail, BadHeaderError
  276. from django.http import HttpResponse, HttpResponseRedirect
  277. from django.contrib import messages
  278.  
  279. import string
  280. import random
  281. import googlemaps
  282. import datetime
  283.  
  284. GOOGLE_API_KEY = 'AIzaSyBhMPu5yfGH1CdqFXO7yiksGlgNR4T07J0'
  285. # Create your views here.
  286.  
  287. def signup(request):
  288. if request.method == 'POST':
  289. form = UserCreationForm(request.POST)
  290. if form.is_valid():
  291. form.save()
  292. username = form.cleaned_data.get('username')
  293. password = form.cleaned_data.get('password1')
  294. user = authenticate(username=username, password=password)
  295. login(request, user)
  296. return redirect('/accounts/profile')
  297. else:
  298. form = UserCreationForm()
  299. return render(request, 'accounts/signup.html', {'form': form})
  300.  
  301.  
  302. def edit_profile(request):
  303. if request.method == 'POST':
  304. form = ProfileForm(request.POST, instance=request.user)
  305. if form.is_valid():
  306. form.save()
  307. return render(request, 'accounts/settings.html')
  308. else:
  309. form = ProfileForm(instance=request.user)
  310. return render(request, 'accounts/settings.html', {'form': form})
  311.  
  312. # user organization information
  313.  
  314. def get_main(request):
  315. return render(request, 'search/main_page.html')
  316.  
  317. def error_msg(request):
  318. messages.error(request, '⚠ Пожалуйста выберите начальную и конечную точку маршрута')
  319. return redirect('main_page')
  320.  
  321. def reg_msg(request):
  322. messages.error(request, '⚠ Вы уже записаны на данный рейс')
  323. return redirect('main_page')
  324.  
  325. def ok_msg(request):
  326. messages.error(request, '✔ Вы успешно зарегестрированы на рейс')
  327. return redirect('user_routes')
  328.  
  329. def get_search(request):
  330. queryset_list = Schedule.objects.all()
  331. if request.method == 'GET':
  332. #query = request.GET.get('start', 'final')
  333. start = request.GET['start']
  334. final = request.GET['final']
  335. date = request.GET['date']
  336. time = request.GET['time']
  337. if start and final:
  338. queryset_list = queryset_list.filter(
  339. Q(org_route__start_place__icontains=start) & Q(org_route__final_place__icontains=final)
  340. )
  341. elif start and final and date:
  342. queryset_list = queryset_list.filter(
  343. Q(org_route__start_place__icontains=start) & Q(org_route__final_place__icontains=final) & Q(date__icontains=date)
  344. )
  345. else:
  346. error_msg(request)
  347. context = {
  348. 'queryset': queryset_list,
  349. }
  350. return render(request, 'search/main_page.html', context)
  351.  
  352. def check_operator_status(user):
  353. employee = Employee.objects.get(name=user)
  354. if employee.status == 'OP':
  355. return True
  356.  
  357. def get_employees(request):
  358. user = request.user
  359. organization = user.employee.organization
  360. if organization:
  361. employees = Employee.objects.filter(organization=organization)
  362. status = check_operator_status(user)
  363. return render(request, 'organization/employees_page.html', {'employees': employees, 'status': status})
  364.  
  365. def get_cars(request):
  366. user = request.user
  367. organization = user.employee.organization
  368. if organization:
  369. cars = Car.objects.filter(organization=organization)
  370. status = check_operator_status(user)
  371. return render(request, 'organization/cars_page.html', {'cars': cars, 'status': status})
  372.  
  373. def get_routes(request):
  374. user = request.user
  375. organization = user.employee.organization
  376. if organization:
  377. routes = OrganizationRoute.objects.filter(organization=organization)
  378. status = check_operator_status(user)
  379. return render(request, 'organization/routes_page.html', {'routes': routes, 'status': status})
  380.  
  381. def get_stats(request):
  382. user = request.user
  383. organization = user.employee.organization
  384. if organization:
  385. routes = OrganizationRoute.objects.filter(organization=organization)
  386. return render(request, 'organization/statistic_page.html', {'routes': routes})
  387.  
  388. def add_route(request):
  389. gmaps = googlemaps.Client(key=GOOGLE_API_KEY)
  390. if request.method == 'POST':
  391. organization = request.user.employee.organization
  392. form = OrganizationRoutesForm(request.POST)
  393. if form.is_valid():
  394. car = form.save(commit=False)
  395. car.organization = organization
  396. car.save()
  397. return render(request, 'organization/add_page.html')
  398. else:
  399. form = OrganizationRoutesForm()
  400. return render(request, 'organization/add_page.html', {'form': form})
  401.  
  402. def add_car(request):
  403. if request.method == 'POST':
  404. organization = request.user.employee.organization
  405. form = OrganizationCarsForm(request.POST)
  406. if form.is_valid():
  407. route = form.save(commit=False)
  408. route.organization = organization
  409. route.save()
  410. return render(request, 'organization/add_page.html')
  411. else:
  412. form = OrganizationCarsForm()
  413. return render(request, 'organization/add_page.html', {'form': form})
  414.  
  415. def add_schedule(request):
  416. if request.method == 'POST':
  417. organization = request.user.employee.organization
  418. form = ScheduleForm(request.POST)
  419. if form.is_valid():
  420. car = form.save(commit=False)
  421. car.organization = organization
  422. car.save()
  423. return render(request, 'organization/add_page.html')
  424. else:
  425. form = ScheduleForm()
  426. return render(request, 'organization/add_page.html', {'form': form})
  427.  
  428. def new_organization(request):
  429. if request.method == 'POST':
  430. form = OrganizationForm(request.POST)
  431. if form.is_valid():
  432. form.save()
  433. name = form.cleaned_data.get('name')
  434. unp = form.cleaned_data.get('unp')
  435. return redirect('login')
  436. else:
  437. form = OrganizationForm()
  438. return render(request, 'accounts/new_org.html', {'form': form})
  439.  
  440. def get_user_routes(request):
  441. user = request.user
  442. # driver = user.employee.schedule
  443. if user:
  444. flight = Flight.objects.filter(user=user)
  445. return render(request, 'accounts/user_routes.html', {'flight': flight})
  446.  
  447. def flight_activate(request):
  448. if request.method == 'POST':
  449. user = request.user
  450. schdule = request.POST.get('route')
  451. kek = Schedule.objects.get(id=schdule)
  452. flight = Flight.objects.filter(user=user)
  453. if schdule:
  454. if flight:
  455. reg_msg(request)
  456. else:
  457. Flight.objects.create(schedule=kek, user=user, status=False)
  458. ok_msg(request)
  459. return redirect('main_page')
  460.  
  461.  
  462. # invite 'system'
  463. def send_invite_code(request):
  464. # code settings
  465. user = request.user
  466. organization = user.employee.organization
  467. code = ''.join(random.choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(10))
  468.  
  469. # email settings
  470. subject = 'Buster invite code'
  471. message = 'Hi! You invited to drivers company. If you not Roman - welcome to service. Invite url - http://localhost:8000/accounts/organization/{}'.format(code)
  472. from_message = request.POST.get('from_invite', '')
  473.  
  474. if from_message and check_operator_status(user):
  475. try:
  476. send_mail(subject, message, 'invites@buster.io', [from_message])
  477. OrganizationInvite.objects.create(code=code, organization=organization)
  478. except BadHeaderError:
  479. return HttpResponse('invalid header')
  480. return redirect('/accounts/organization')
  481. else:
  482. return HttpResponse('Make sure all fields are entered and valid.')
  483.  
  484. def join_invite(request, invite):
  485. user = request.user
  486. invite_code = OrganizationInvite.objects.get(code=invite)
  487. if invite_code:
  488. Employee.objects.create(
  489. name=user,
  490. status='DR',
  491. hire_date=datetime.date.today(),
  492. hire_end_date=datetime.date.today(),
  493. organization=invite_code.organization
  494. )
  495. invite_code.delete()
  496. var gulp = require('gulp'),
  497. gutil = require('gulp-util' ),
  498. sass = require('gulp-sass'),
  499. browserSync = require('browser-sync'),
  500. concat = require('gulp-concat'),
  501. uglify = require('gulp-uglify'),
  502. cleanCSS = require('gulp-clean-css'),
  503. rename = require('gulp-rename'),
  504. del = require('del'),
  505. imagemin = require('gulp-imagemin'),
  506. cache = require('gulp-cache'),
  507. autoprefixer = require('gulp-autoprefixer'),
  508. ftp = require('vinyl-ftp'),
  509. notify = require("gulp-notify"),
  510. rsync = require('gulp-rsync');
  511.  
  512. // Пользовательские скрипты проекта
  513.  
  514. gulp.task('common-js', function() {
  515. return gulp.src([
  516. 'app/js/common.js',
  517. ])
  518. .pipe(concat('common.min.js'))
  519. .pipe(uglify())
  520. .pipe(gulp.dest('app/js'));
  521. });
  522.  
  523. gulp.task('js', ['common-js'], function() {
  524. return gulp.src([
  525. 'app/libs/jquery/dist/jquery.min.js',
  526. 'app/js/common.min.js', // Всегда в конце
  527. ])
  528. .pipe(concat('scripts.min.js'))
  529. // .pipe(uglify()) // Минимизировать весь js (на выбор)
  530. .pipe(gulp.dest('app/js'))
  531. .pipe(browserSync.reload({stream: true}));
  532. });
  533.  
  534. gulp.task('browser-sync', function() {
  535. browserSync({
  536. server: {
  537. baseDir: 'app'
  538. },
  539. notify: false,
  540. // tunnel: true,
  541. // tunnel: "projectmane", //Demonstration page: http://projectmane.localtunnel.me
  542. });
  543. });
  544.  
  545. gulp.task('sass', function() {
  546. return gulp.src('app/sass/**/*.sass')
  547. .pipe(sass({outputStyle: 'expand'}).on("error", notify.onError()))
  548. .pipe(rename({suffix: '.min', prefix : ''}))
  549. .pipe(autoprefixer(['last 15 versions']))
  550. .pipe(cleanCSS()) // Опционально, закомментировать при отладке
  551. .pipe(gulp.dest('app/css'))
  552. .pipe(browserSync.reload({stream: true}));
  553. });
  554.  
  555. gulp.task('watch', ['sass', 'js', 'browser-sync'], function() {
  556. gulp.watch('app/sass/**/*.sass', ['sass']);
  557. gulp.watch(['libs/**/*.js', 'app/js/common.js'], ['js']);
  558. gulp.watch('app/*.html', browserSync.reload);
  559. });
  560.  
  561. gulp.task('imagemin', function() {
  562. return gulp.src('app/img/**/*')
  563. .pipe(cache(imagemin())) // Cache Images
  564. .pipe(gulp.dest('dist/img'));
  565. });
  566.  
  567. gulp.task('build', ['removedist', 'imagemin', 'sass', 'js'], function() {
  568.  
  569. var buildFiles = gulp.src([
  570. 'app/*.html',
  571. 'app/.htaccess',
  572. ]).pipe(gulp.dest('dist'));
  573.  
  574. var buildCss = gulp.src([
  575. 'app/css/main.min.css',
  576. ]).pipe(gulp.dest('dist/css'));
  577.  
  578. var buildJs = gulp.src([
  579. 'app/js/scripts.min.js',
  580. ]).pipe(gulp.dest('dist/js'));
  581.  
  582. var buildFonts = gulp.src([
  583. 'app/fonts/**/*',
  584. ]).pipe(gulp.dest('dist/fonts'));
  585.  
  586. });
  587.  
  588. gulp.task('deploy', function() {
  589.  
  590. var conn = ftp.create({
  591. host: 'hostname.com',
  592. user: 'username',
  593. password: 'userpassword',
  594. parallel: 10,
  595. log: gutil.log
  596. });
  597.  
  598. var globs = [
  599. 'dist/**',
  600. 'dist/.htaccess',
  601. ];
  602. return gulp.src(globs, {buffer: false})
  603. .pipe(conn.dest('/path/to/folder/on/server'));
  604.  
  605. });
  606.  
  607. gulp.task('rsync', function() {
  608. return gulp.src('dist/**')
  609. .pipe(rsync({
  610. root: 'dist/',
  611. hostname: 'username@yousite.com',
  612. destination: 'yousite/public_html/',
  613. // include: ['*.htaccess'], // Скрытые файлы, которые необходимо включить в деплой
  614. recursive: true,
  615. archive: true,
  616. silent: false,
  617. compress: true
  618. }));
  619. });
  620.  
  621. gulp.task('removedist', function() { return del.sync('dist'); });
  622. gulp.task('clearcache', function () { return cache.clearAll(); });
  623.  
  624. gulp.task('default', ['watch']);
  625. bal gettext, interpolate, ngettext*/
  626. (function($) {
  627. 'use strict';
  628. var lastChecked;
  629.  
  630. $.fn.actions = function(opts) {
  631. var options = $.extend({}, $.fn.actions.defaults, opts);
  632. var actionCheckboxes = $(this);
  633. var list_editable_changed = false;
  634. var showQuestion = function() {
  635. $(options.acrossClears).hide();
  636. $(options.acrossQuestions).show();
  637. $(options.allContainer).hide();
  638. },
  639. showClear = function() {
  640. $(options.acrossClears).show();
  641. $(options.acrossQuestions).hide();
  642. $(options.actionContainer).toggleClass(options.selectedClass);
  643. $(options.allContainer).show();
  644. $(options.counterContainer).hide();
  645. },
  646. reset = function() {
  647. $(options.acrossClears).hide();
  648. $(options.acrossQuestions).hide();
  649. $(options.allContainer).hide();
  650. $(options.counterContainer).show();
  651. },
  652. clearAcross = function() {
  653. reset();
  654. $(options.acrossInput).val(0);
  655. $(options.actionContainer).removeClass(options.selectedClass);
  656. },
  657. checker = function(checked) {
  658. if (checked) {
  659. showQuestion();
  660. } else {
  661. reset();
  662. }
  663. $(actionCheckboxes).prop("checked", checked)
  664. .parent().parent().toggleClass(options.selectedClass, checked);
  665. },
  666. updateCounter = function() {
  667. var sel = $(actionCheckboxes).filter(":checked").length;
  668. // data-actions-icnt is defined in the generated HTML
  669. // and contains the total amount of objects in the queryset
  670. var actions_icnt = $('.action-counter').data('actionsIcnt');
  671. $(options.counterContainer).html(interpolate(
  672. ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
  673. sel: sel,
  674. cnt: actions_icnt
  675. }, true));
  676. $(options.allToggle).prop("checked", function() {
  677. var value;
  678. if (sel === actionCheckboxes.length) {
  679. value = true;
  680. showQuestion();
  681. } else {
  682. value = false;
  683. clearAcross();
  684. }
  685. return value;
  686. });
  687. };
  688. // Show counter by default
  689. $(options.counterContainer).show();
  690. // Check state of checkboxes and reinit state if needed
  691. $(this).filter(":checked").each(function(i) {
  692. $(this).parent().parent().toggleClass(options.selectedClass);
  693. updateCounter();
  694. if ($(options.acrossInput).val() === 1) {
  695. showClear();
  696. }
  697. });
  698. $(options.allToggle).show().click(function() {
  699. checker($(this).prop("checked"));
  700. updateCounter();
  701. });
  702. $("a", options.acrossQuestions).click(function(event) {
  703. event.preventDefault();
  704. $(options.acrossInput).val(1);
  705. showClear();
  706. });
  707. $("a", options.acrossClears).click(function(event) {
  708. event.preventDefault();
  709. $(options.allToggle).prop("checked", false);
  710. clearAcross();
  711. checker(0);
  712. updateCounter();
  713. });
  714. lastChecked = null;
  715. $(actionCheckboxes).click(function(event) {
  716. if (!event) { event = window.event; }
  717. var target = event.target ? event.target : event.srcElement;
  718. if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) {
  719. var inrange = false;
  720. $(lastChecked).prop("checked", target.checked)
  721. .parent().parent().toggleClass(options.selectedClass, target.checked);
  722. $(actionCheckboxes).each(function() {
  723. if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) {
  724. inrange = (inrange) ? false : true;
  725. }
  726. if (inrange) {
  727. $(this).prop("checked", target.checked)
  728. .parent().parent().toggleClass(options.selectedClass, target.checked);
  729. }
  730. });
  731. }
  732. $(target).parent().parent().toggleClass(options.selectedClass, target.checked);
  733. lastChecked = target;
  734. updateCounter();
  735. });
  736. $('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() {
  737. list_editable_changed = true;
  738. });
  739. $('form#changelist-form button[name="index"]').click(function(event) {
  740. if (list_editable_changed) {
  741. return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
  742. }
  743. });
  744. $('form#changelist-form input[name="_save"]').click(function(event) {
  745. var action_changed = false;
  746. $('select option:selected', options.actionContainer).each(function() {
  747. if ($(this).val()) {
  748. action_changed = true;
  749. }
  750. });
  751. if (action_changed) {
  752. if (list_editable_changed) {
  753. return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action."));
  754. } else {
  755. return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."));
  756. }
  757. }
  758. });
  759. };
  760. /* Setup plugin defaults */
  761. $.fn.actions.defaults = {
  762. actionContainer: "div.actions",
  763. counterContainer: "span.action-counter",
  764. allContainer: "div.actions span.all",
  765. acrossInput: "div.actions input.select-across",
  766. acrossQuestions: "div.actions span.question",
  767. acrossClears: "div.actions span.clear",
  768. allToggle: "#action-toggle",
  769. selectedClass: "selected"
  770. };
  771. $(document).ready(function() {
  772. var $actionsEls = $('tr input.action-select');
  773. if ($actionsEls.length > 0) {
  774. $actionsEls.actions();
  775. }
  776. });
  777. })(django.jQuery);
  778.  
  779. var isOpera = (navigator.userAgent.indexOf("Opera") >= 0) && parseFloat(navigator.appVersion);
  780. var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]);
  781.  
  782. // quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]);
  783. function quickElement() {
  784. 'use strict';
  785. var obj = document.createElement(arguments[0]);
  786. if (arguments[2]) {
  787. var textNode = document.createTextNode(arguments[2]);
  788. obj.appendChild(textNode);
  789. }
  790. var len = arguments.length;
  791. for (var i = 3; i < len; i += 2) {
  792. obj.setAttribute(arguments[i], arguments[i + 1]);
  793. }
  794. arguments[1].appendChild(obj);
  795. return obj;
  796. }
  797.  
  798. // "a" is reference to an object
  799. function removeChildren(a) {
  800. 'use strict';
  801. while (a.hasChildNodes()) {
  802. a.removeChild(a.lastChild);
  803. }
  804. }
  805.  
  806. // ----------------------------------------------------------------------------
  807. // Find-position functions by PPK
  808. // See http://www.quirksmode.org/js/findpos.html
  809. // ----------------------------------------------------------------------------
  810. function findPosX(obj) {
  811. 'use strict';
  812. var curleft = 0;
  813. if (obj.offsetParent) {
  814. while (obj.offsetParent) {
  815. curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft);
  816. obj = obj.offsetParent;
  817. }
  818. // IE offsetParent does not include the top-level
  819. if (isIE && obj.parentElement) {
  820. curleft += obj.offsetLeft - obj.scrollLeft;
  821. }
  822. } else if (obj.x) {
  823. curleft += obj.x;
  824. }
  825. return curleft;
  826. }
  827.  
  828. function findPosY(obj) {
  829. 'use strict';
  830. var curtop = 0;
  831. if (obj.offsetParent) {
  832. while (obj.offsetParent) {
  833. curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop);
  834. obj = obj.offsetParent;
  835. }
  836. // IE offsetParent does not include the top-level
  837. if (isIE && obj.parentElement) {
  838. curtop += obj.offsetTop - obj.scrollTop;
  839. }
  840. } else if (obj.y) {
  841. curtop += obj.y;
  842. }
  843. return curtop;
  844. }
  845.  
  846. //-----------------------------------------------------------------------------
  847. // Date object extensions
  848. // ----------------------------------------------------------------------------
  849. (function() {
  850. 'use strict';
  851. Date.prototype.getTwelveHours = function() {
  852. var hours = this.getHours();
  853. if (hours === 0) {
  854. return 12;
  855. }
  856. else {
  857. return hours <= 12 ? hours : hours - 12;
  858. }
  859. };
  860.  
  861. Date.prototype.getTwoDigitMonth = function() {
  862. return (this.getMonth() < 9) ? '0' + (this.getMonth() + 1) : (this.getMonth() + 1);
  863. };
  864.  
  865. Date.prototype.getTwoDigitDate = function() {
  866. return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate();
  867. };
  868.  
  869. Date.prototype.getTwoDigitTwelveHour = function() {
  870. return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours();
  871. };
  872.  
  873. Date.prototype.getTwoDigitHour = function() {
  874. return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours();
  875. };
  876.  
  877. Date.prototype.getTwoDigitMinute = function() {
  878. return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
  879. };
  880.  
  881. Date.prototype.getTwoDigitSecond = function() {
  882. return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
  883. };
  884.  
  885. Date.prototype.getHourMinute = function() {
  886. return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute();
  887. };
  888.  
  889. Date.prototype.getHourMinuteSecond = function() {
  890. return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond();
  891. };
  892.  
  893. Date.prototype.getFullMonthName = function() {
  894. return typeof window.CalendarNamespace === "undefined"
  895. ? this.getTwoDigitMonth()
  896. : window.CalendarNamespace.monthsOfYear[this.getMonth()];
  897. };
  898.  
  899. Date.prototype.strftime = function(format) {
  900. var fields = {
  901. B: this.getFullMonthName(),
  902. c: this.toString(),
  903. d: this.getTwoDigitDate(),
  904. H: this.getTwoDigitHour(),
  905. I: this.getTwoDigitTwelveHour(),
  906. m: this.getTwoDigitMonth(),
  907. M: this.getTwoDigitMinute(),
  908. p: (this.getHours() >= 12) ? 'PM' : 'AM',
  909. S: this.getTwoDigitSecond(),
  910. w: '0' + this.getDay(),
  911. x: this.toLocaleDateString(),
  912. X: this.toLocaleTimeString(),
  913. y: ('' + this.getFullYear()).substr(2, 4),
  914. Y: '' + this.getFullYear(),
  915. '%': '%'
  916. };
  917. var result = '', i = 0;
  918. while (i < format.length) {
  919. if (format.charAt(i) === '%') {
  920. result = result + fields[format.charAt(i + 1)];
  921. ++i;
  922. }
  923. else {
  924. result = result + format.charAt(i);
  925. }
  926. ++i;
  927. }
  928. return result;
  929. };
  930.  
  931. // ----------------------------------------------------------------------------
  932. // String object extensions
  933. // ----------------------------------------------------------------------------
  934. String.prototype.pad_left = function(pad_length, pad_string) {
  935. var new_string = this;
  936. for (var i = 0; new_string.length < pad_length; i++) {
  937. new_string = pad_string + new_string;
  938. }
  939. return new_string;
  940. };
  941.  
  942. String.prototype.strptime = function(format) {
  943. var split_format = format.split(/[.\-/]/);
  944. var date = this.split(/[.\-/]/);
  945. var i = 0;
  946. var day, month, year;
  947. while (i < split_format.length) {
  948. switch (split_format[i]) {
  949. case "%d":
  950. day = date[i];
  951. break;
  952. case "%m":
  953. month = date[i] - 1;
  954. break;
  955. case "%Y":
  956. year = date[i];
  957. break;
  958. case "%y":
  959. year = date[i];
  960. break;
  961. }
  962. ++i;
  963. }
  964. // Create Date object from UTC since the parsed value is supposed to be
  965. // in UTC, not local time. Also, the calendar uses UTC functions for
  966. // date extraction.
  967. return new Date(Date.UTC(year, month, day));
  968. };
  969.  
  970. })();
  971.  
  972. function getStyle(oElm, strCssRule) {
  973. 'use strict';
  974. var strValue = "";
  975. if(document.defaultView && document.defaultView.getComputedStyle) {
  976. strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
  977. }
  978. else if(oElm.currentStyle) {
  979. strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1) {
  980. return p1.toUpperCase();
  981. });
  982. strValue = oElm.currentStyle[strCssRule];
  983. }
  984. return strValue;
  985. }
  986. (function($) {
  987. 'use strict';
  988. $.fn.formset = function(opts) {
  989. var options = $.extend({}, $.fn.formset.defaults, opts);
  990. var $this = $(this);
  991. var $parent = $this.parent();
  992. var updateElementIndex = function(el, prefix, ndx) {
  993. var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
  994. var replacement = prefix + "-" + ndx;
  995. if ($(el).prop("for")) {
  996. $(el).prop("for", $(el).prop("for").replace(id_regex, replacement));
  997. }
  998. if (el.id) {
  999. el.id = el.id.replace(id_regex, replacement);
  1000. }
  1001. if (el.name) {
  1002. el.name = el.name.replace(id_regex, replacement);
  1003. }
  1004. };
  1005. var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off");
  1006. var nextIndex = parseInt(totalForms.val(), 10);
  1007. var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off");
  1008. // only show the add button if we are allowed to add more items,
  1009. // note that max_num = None translates to a blank string.
  1010. var showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0;
  1011. $this.each(function(i) {
  1012. $(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
  1013. });
  1014. if ($this.length && showAddButton) {
  1015. var addButton = options.addButton;
  1016. if (addButton === null) {
  1017. if ($this.prop("tagName") === "TR") {
  1018. // If forms are laid out as table rows, insert the
  1019. // "add" button in a new table row:
  1020. var numCols = this.eq(-1).children().length;
  1021. $parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="#">' + options.addText + "</a></tr>");
  1022. addButton = $parent.find("tr:last a");
  1023. } else {
  1024. // Otherwise, insert it immediately after the last form:
  1025. $this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="#">' + options.addText + "</a></div>");
  1026. addButton = $this.filter(":last").next().find("a");
  1027. }
  1028. }
  1029. addButton.click(function(e) {
  1030. e.preventDefault();
  1031. var template = $("#" + options.prefix + "-empty");
  1032. var row = template.clone(true);
  1033. row.removeClass(options.emptyCssClass)
  1034. .addClass(options.formCssClass)
  1035. .attr("id", options.prefix + "-" + nextIndex);
  1036. if (row.is("tr")) {
  1037. // If the forms are laid out in table rows, insert
  1038. // the remove button into the last table cell:
  1039. row.children(":last").append('<div><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></div>");
  1040. } else if (row.is("ul") || row.is("ol")) {
  1041. // If they're laid out as an ordered/unordered list,
  1042. // insert an <li> after the last list item:
  1043. row.append('<li><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></li>");
  1044. } else {
  1045. // Otherwise, just insert the remove button as the
  1046. // last child element of the form's container:
  1047. row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></span>");
  1048. }
  1049. row.find("*").each(function() {
  1050. updateElementIndex(this, options.prefix, totalForms.val());
  1051. });
  1052. // Insert the new form when it has been fully edited
  1053. row.insertBefore($(template));
  1054. // Update number of total forms
  1055. $(totalForms).val(parseInt(totalForms.val(), 10) + 1);
  1056. nextIndex += 1;
  1057. // Hide add button in case we've hit the max, except we want to add infinitely
  1058. if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
  1059. addButton.parent().hide();
  1060. }
  1061. // The delete button of each row triggers a bunch of other things
  1062. row.find("a." + options.deleteCssClass).click(function(e1) {
  1063. e1.preventDefault();
  1064. // Remove the parent form containing this button:
  1065. row.remove();
  1066. nextIndex -= 1;
  1067. // If a post-delete callback was provided, call it with the deleted form:
  1068. if (options.removed) {
  1069. options.removed(row);
  1070. }
  1071. $(document).trigger('formset:removed', [row, options.prefix]);
  1072. // Update the TOTAL_FORMS form count.
  1073. var forms = $("." + options.formCssClass);
  1074. $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
  1075. // Show add button again once we drop below max
  1076. if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) {
  1077. addButton.parent().show();
  1078. }
  1079. // Also, update names and ids for all remaining form controls
  1080. // so they remain in sequence:
  1081. var i, formCount;
  1082. var updateElementCallback = function() {
  1083. updateElementIndex(this, options.prefix, i);
  1084. };
  1085. for (i = 0, formCount = forms.length; i < formCount; i++) {
  1086. updateElementIndex($(forms).get(i), options.prefix, i);
  1087. $(forms.get(i)).find("*").each(updateElementCallback);
  1088. }
  1089. });
  1090. // If a post-add callback was supplied, call it with the added form:
  1091. if (options.added) {
  1092. options.added(row);
  1093. }
  1094. $(document).trigger('formset:added', [row, options.prefix]);
  1095. });
  1096. }
  1097. return this;
  1098. };
  1099.  
  1100. /* Setup plugin defaults */
  1101. $.fn.formset.defaults = {
  1102. prefix: "form", // The form prefix for your django formset
  1103. addText: "add another", // Text for the add link
  1104. deleteText: "remove", // Text for the delete link
  1105. addCssClass: "add-row", // CSS class applied to the add link
  1106. deleteCssClass: "delete-row", // CSS class applied to the delete link
  1107. emptyCssClass: "empty-row", // CSS class applied to the empty row
  1108. formCssClass: "dynamic-form", // CSS class applied to each form in a formset
  1109. added: null, // Function called each time a new form is added
  1110. removed: null, // Function called each time a form is deleted
  1111. addButton: null // Existing add button to use
  1112. };
  1113.  
  1114.  
  1115. // Tabular inlines ---------------------------------------------------------
  1116. $.fn.tabularFormset = function(options) {
  1117. var $rows = $(this);
  1118. var alternatingRows = function(row) {
  1119. $($rows.selector).not(".add-row").removeClass("row1 row2")
  1120. .filter(":even").addClass("row1").end()
  1121. .filter(":odd").addClass("row2");
  1122. };
  1123.  
  1124. var reinitDateTimeShortCuts = function() {
  1125. // Reinitialize the calendar and clock widgets by force
  1126. if (typeof DateTimeShortcuts !== "undefined") {
  1127. $(".datetimeshortcuts").remove();
  1128. DateTimeShortcuts.init();
  1129. }
  1130. };
  1131.  
  1132. var updateSelectFilter = function() {
  1133. // If any SelectFilter widgets are a part of the new form,
  1134. // instantiate a new SelectFilter instance for it.
  1135. if (typeof SelectFilter !== 'undefined') {
  1136. $('.selectfilter').each(function(index, value) {
  1137. var namearr = value.name.split('-');
  1138. SelectFilter.init(value.id, namearr[namearr.length - 1], false);
  1139. });
  1140. $('.selectfilterstacked').each(function(index, value) {
  1141. var namearr = value.name.split('-');
  1142. SelectFilter.init(value.id, namearr[namearr.length - 1], true);
  1143. });
  1144. }
  1145. };
  1146.  
  1147. var initPrepopulatedFields = function(row) {
  1148. row.find('.prepopulated_field').each(function() {
  1149. var field = $(this),
  1150. input = field.find('input, select, textarea'),
  1151. dependency_list = input.data('dependency_list') || [],
  1152. dependencies = [];
  1153. $.each(dependency_list, function(i, field_name) {
  1154. dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
  1155. });
  1156. if (dependencies.length) {
  1157. input.prepopulate(dependencies, input.attr('maxlength'));
  1158. }
  1159. });
  1160. };
  1161.  
  1162. $rows.formset({
  1163. prefix: options.prefix,
  1164. addText: options.addText,
  1165. formCssClass: "dynamic-" + options.prefix,
  1166. deleteCssClass: "inline-deletelink",
  1167. deleteText: options.deleteText,
  1168. emptyCssClass: "empty-form",
  1169. removed: alternatingRows,
  1170. added: function(row) {
  1171. initPrepopulatedFields(row);
  1172. reinitDateTimeShortCuts();
  1173. updateSelectFilter();
  1174. alternatingRows(row);
  1175. },
  1176. addButton: options.addButton
  1177. });
  1178.  
  1179. return $rows;
  1180. };
  1181.  
  1182. // Stacked inlines ---------------------------------------------------------
  1183. $.fn.stackedFormset = function(options) {
  1184. var $rows = $(this);
  1185. var updateInlineLabel = function(row) {
  1186. $($rows.selector).find(".inline_label").each(function(i) {
  1187. var count = i + 1;
  1188. $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
  1189. });
  1190. };
  1191.  
  1192. var reinitDateTimeShortCuts = function() {
  1193. // Reinitialize the calendar and clock widgets by force, yuck.
  1194. if (typeof DateTimeShortcuts !== "undefined") {
  1195. $(".datetimeshortcuts").remove();
  1196. DateTimeShortcuts.init();
  1197. }
  1198. };
  1199.  
  1200. var updateSelectFilter = function() {
  1201. // If any SelectFilter widgets were added, instantiate a new instance.
  1202. if (typeof SelectFilter !== "undefined") {
  1203. $(".selectfilter").each(function(index, value) {
  1204. var namearr = value.name.split('-');
  1205. SelectFilter.init(value.id, namearr[namearr.length - 1], false);
  1206. });
  1207. $(".selectfilterstacked").each(function(index, value) {
  1208. var namearr = value.name.split('-');
  1209. SelectFilter.init(value.id, namearr[namearr.length - 1], true);
  1210. });
  1211. }
  1212. };
  1213.  
  1214. var initPrepopulatedFields = function(row) {
  1215. row.find('.prepopulated_field').each(function() {
  1216. var field = $(this),
  1217. input = field.find('input, select, textarea'),
  1218. dependency_list = input.data('dependency_list') || [],
  1219. dependencies = [];
  1220. $.each(dependency_list, function(i, field_name) {
  1221. dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
  1222. });
  1223. if (dependencies.length) {
  1224. input.prepopulate(dependencies, input.attr('maxlength'));
  1225. }
  1226. });
  1227. };
  1228.  
  1229. $rows.formset({
  1230. prefix: options.prefix,
  1231. addText: options.addText,
  1232. formCssClass: "dynamic-" + options.prefix,
  1233. deleteCssClass: "inline-deletelink",
  1234. deleteText: options.deleteText,
  1235. emptyCssClass: "empty-form",
  1236. removed: updateInlineLabel,
  1237. added: function(row) {
  1238. initPrepopulatedFields(row);
  1239. reinitDateTimeShortCuts();
  1240. updateSelectFilter();
  1241. updateInlineLabel(row);
  1242. },
  1243. addButton: options.addButton
  1244. });
  1245.  
  1246. return $rows;
  1247. };
  1248.  
  1249. $(document).ready(function() {
  1250. $(".js-inline-admin-formset").each(function() {
  1251. var data = $(this).data(),
  1252. inlineOptions = data.inlineFormset;
  1253. switch(data.inlineType) {
  1254. case "stacked":
  1255. $(inlineOptions.name + "-group .inline-related").stackedFormset(inlineOptions.options);
  1256. break;
  1257. case "tabular":
  1258. $(inlineOptions.name + "-group .tabular.inline-related tbody:first > tr").tabularFormset(inlineOptions.options);
  1259. break;
  1260. }
  1261. });
  1262. });
  1263. })(django.jQuery);
  1264.  
  1265. (function($) {
  1266. 'use strict';
  1267. function findForm(node) {
  1268. // returns the node of the form containing the given node
  1269. if (node.tagName.toLowerCase() !== 'form') {
  1270. return findForm(node.parentNode);
  1271. }
  1272. return node;
  1273. }
  1274.  
  1275. window.SelectFilter = {
  1276. init: function(field_id, field_name, is_stacked) {
  1277. if (field_id.match(/__prefix__/)) {
  1278. // Don't initialize on empty forms.
  1279. return;
  1280. }
  1281. var from_box = document.getElementById(field_id);
  1282. from_box.id += '_from'; // change its ID
  1283. from_box.className = 'filtered';
  1284.  
  1285. var ps = from_box.parentNode.getElementsByTagName('p');
  1286. for (var i = 0; i < ps.length; i++) {
  1287. if (ps[i].className.indexOf("info") !== -1) {
  1288. // Remove <p class="info">, because it just gets in the way.
  1289. from_box.parentNode.removeChild(ps[i]);
  1290. } else if (ps[i].className.indexOf("help") !== -1) {
  1291. // Move help text up to the top so it isn't below the select
  1292. // boxes or wrapped off on the side to the right of the add
  1293. // button:
  1294. from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
  1295. }
  1296. }
  1297.  
  1298. // <div class="selector"> or <div class="selector stacked">
  1299. var selector_div = quickElement('div', from_box.parentNode);
  1300. selector_div.className = is_stacked ? 'selector stacked' : 'selector';
  1301.  
  1302. // <div class="selector-available">
  1303. var selector_available = quickElement('div', selector_div);
  1304. selector_available.className = 'selector-available';
  1305. var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name]));
  1306. quickElement(
  1307. 'span', title_available, '',
  1308. 'class', 'help help-tooltip help-icon',
  1309. 'title', interpolate(
  1310. gettext(
  1311. 'This is the list of available %s. You may choose some by ' +
  1312. 'selecting them in the box below and then clicking the ' +
  1313. '"Choose" arrow between the two boxes.'
  1314. ),
  1315. [field_name]
  1316. )
  1317. );
  1318.  
  1319. var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter');
  1320. filter_p.className = 'selector-filter';
  1321.  
  1322. var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + '_input');
  1323.  
  1324. quickElement(
  1325. 'span', search_filter_label, '',
  1326. 'class', 'help-tooltip search-label-icon',
  1327. 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])
  1328. );
  1329.  
  1330. filter_p.appendChild(document.createTextNode(' '));
  1331.  
  1332. var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
  1333. filter_input.id = field_id + '_input';
  1334.  
  1335. selector_available.appendChild(from_box);
  1336. var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_add_all_link');
  1337. choose_all.className = 'selector-chooseall';
  1338.  
  1339. // <ul class="selector-chooser">
  1340. var selector_chooser = quickElement('ul', selector_div);
  1341. selector_chooser.className = 'selector-chooser';
  1342. var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', '#', 'id', field_id + '_add_link');
  1343. add_link.className = 'selector-add';
  1344. var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', '#', 'id', field_id + '_remove_link');
  1345. remove_link.className = 'selector-remove';
  1346.  
  1347. // <div class="selector-chosen">
  1348. var selector_chosen = quickElement('div', selector_div);
  1349. selector_chosen.className = 'selector-chosen';
  1350. var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
  1351. quickElement(
  1352. 'span', title_chosen, '',
  1353. 'class', 'help help-tooltip help-icon',
  1354. 'title', interpolate(
  1355. gettext(
  1356. 'This is the list of chosen %s. You may remove some by ' +
  1357. 'selecting them in the box below and then clicking the ' +
  1358. '"Remove" arrow between the two boxes.'
  1359. ),
  1360. [field_name]
  1361. )
  1362. );
  1363.  
  1364. var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
  1365. to_box.className = 'filtered';
  1366. var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_remove_all_link');
  1367. clear_all.className = 'selector-clearall';
  1368.  
  1369. from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
  1370.  
  1371. // Set up the JavaScript event handlers for the select box filter interface
  1372. var move_selection = function(e, elem, move_func, from, to) {
  1373. if (elem.className.indexOf('active') !== -1) {
  1374. move_func(from, to);
  1375. SelectFilter.refresh_icons(field_id);
  1376. }
  1377. e.preventDefault();
  1378. };
  1379. choose_all.addEventListener('click', function(e) {
  1380. move_selection(e, this, SelectBox.move_all, field_id + '_from', field_id + '_to');
  1381. });
  1382. add_link.addEventListener('click', function(e) {
  1383. move_selection(e, this, SelectBox.move, field_id + '_from', field_id + '_to');
  1384. });
  1385. remove_link.addEventListener('click', function(e) {
  1386. move_selection(e, this, SelectBox.move, field_id + '_to', field_id + '_from');
  1387. });
  1388. clear_all.addEventListener('click', function(e) {
  1389. move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from');
  1390. });
  1391. filter_input.addEventListener('keypress', function(e) {
  1392. SelectFilter.filter_key_press(e, field_id);
  1393. });
  1394. filter_input.addEventListener('keyup', function(e) {
  1395. SelectFilter.filter_key_up(e, field_id);
  1396. });
  1397. filter_input.addEventListener('keydown', function(e) {
  1398. SelectFilter.filter_key_down(e, field_id);
  1399. });
  1400. selector_div.addEventListener('change', function(e) {
  1401. if (e.target.tagName === 'SELECT') {
  1402. SelectFilter.refresh_icons(field_id);
  1403. }
  1404. });
  1405. selector_div.addEventListener('dblclick', function(e) {
  1406. if (e.target.tagName === 'OPTION') {
  1407. if (e.target.closest('select').id === field_id + '_to') {
  1408. SelectBox.move(field_id + '_to', field_id + '_from');
  1409. } else {
  1410. SelectBox.move(field_id + '_from', field_id + '_to');
  1411. }
  1412. SelectFilter.refresh_icons(field_id);
  1413. }
  1414. });
  1415. findForm(from_box).addEventListener('submit', function() {
  1416. SelectBox.select_all(field_id + '_to');
  1417. });
  1418. SelectBox.init(field_id + '_from');
  1419. SelectBox.init(field_id + '_to');
  1420. // Move selected from_box options to to_box
  1421. SelectBox.move(field_id + '_from', field_id + '_to');
  1422.  
  1423. if (!is_stacked) {
  1424. // In horizontal mode, give the same height to the two boxes.
  1425. var j_from_box = $(from_box);
  1426. var j_to_box = $(to_box);
  1427. var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); };
  1428. if (j_from_box.outerHeight() > 0) {
  1429. resize_filters(); // This fieldset is already open. Resize now.
  1430. } else {
  1431. // This fieldset is probably collapsed. Wait for its 'show' event.
  1432. j_to_box.closest('fieldset').one('show.fieldset', resize_filters);
  1433. }
  1434. }
  1435.  
  1436. // Initial icon refresh
  1437. SelectFilter.refresh_icons(field_id);
  1438. },
  1439. any_selected: function(field) {
  1440. var any_selected = false;
  1441. try {
  1442. // Temporarily add the required attribute and check validity.
  1443. // This is much faster in WebKit browsers than the fallback.
  1444. field.attr('required', 'required');
  1445. any_selected = field.is(':valid');
  1446. field.removeAttr('required');
  1447. } catch (e) {
  1448. // Browsers that don't support :valid (IE < 10)
  1449. any_selected = field.find('option:selected').length > 0;
  1450. }
  1451. return any_selected;
  1452. },
  1453. refresh_icons: function(field_id) {
  1454. var from = $('#' + field_id + '_from');
  1455. var to = $('#' + field_id + '_to');
  1456. // Active if at least one item is selected
  1457. $('#' + field_id + '_add_link').toggleClass('active', SelectFilter.any_selected(from));
  1458. $('#' + field_id + '_remove_link').toggleClass('active', SelectFilter.any_selected(to));
  1459. // Active if the corresponding box isn't empty
  1460. $('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0);
  1461. $('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0);
  1462. },
  1463. filter_key_press: function(event, field_id) {
  1464. var from = document.getElementById(field_id + '_from');
  1465. // don't submit form if user pressed Enter
  1466. if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
  1467. from.selectedIndex = 0;
  1468. SelectBox.move(field_id + '_from', field_id + '_to');
  1469. from.selectedIndex = 0;
  1470. event.preventDefault();
  1471. return false;
  1472. }
  1473. },
  1474. filter_key_up: function(event, field_id) {
  1475. var from = document.getElementById(field_id + '_from');
  1476. var temp = from.selectedIndex;
  1477. SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
  1478. from.selectedIndex = temp;
  1479. return true;
  1480. },
  1481. filter_key_down: function(event, field_id) {
  1482. var from = document.getElementById(field_id + '_from');
  1483. // right arrow -- move across
  1484. if ((event.which && event.which === 39) || (event.keyCode && event.keyCode === 39)) {
  1485. var old_index = from.selectedIndex;
  1486. SelectBox.move(field_id + '_from', field_id + '_to');
  1487. from.selectedIndex = (old_index === from.length) ? from.length - 1 : old_index;
  1488. return false;
  1489. }
  1490. // down arrow -- wrap around
  1491. if ((event.which && event.which === 40) || (event.keyCode && event.keyCode === 40)) {
  1492. from.selectedIndex = (from.length === from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
  1493. }
  1494. // up arrow -- wrap around
  1495. if ((event.which && event.which === 38) || (event.keyCode && event.keyCode === 38)) {
  1496. from.selectedIndex = (from.selectedIndex === 0) ? from.length - 1 : from.selectedIndex - 1;
  1497. }
  1498. return true;
  1499. }
  1500. };
  1501.  
  1502. window.addEventListener('load', function(e) {
  1503. $('select.selectfilter, select.selectfilterstacked').each(function() {
  1504. var $el = $(this),
  1505. data = $el.data();
  1506. SelectFilter.init($el.attr('id'), data.fieldName, parseInt(data.isStacked, 10));
  1507. });
  1508. });
  1509.  
  1510. })(django.jQuery);
  1511. (function() {
  1512. 'use strict';
  1513. var timeParsePatterns = [
  1514. // 9
  1515. {
  1516. re: /^\d{1,2}$/i,
  1517. handler: function(bits) {
  1518. if (bits[0].length === 1) {
  1519. return '0' + bits[0] + ':00';
  1520. } else {
  1521. return bits[0] + ':00';
  1522. }
  1523. }
  1524. },
  1525. // 13:00
  1526. {
  1527. re: /^\d{2}[:.]\d{2}$/i,
  1528. handler: function(bits) {
  1529. return bits[0].replace('.', ':');
  1530. }
  1531. },
  1532. // 9:00
  1533. {
  1534. re: /^\d[:.]\d{2}$/i,
  1535. handler: function(bits) {
  1536. return '0' + bits[0].replace('.', ':');
  1537. }
  1538. },
  1539. // 3 am / 3 a.m. / 3am
  1540. {
  1541. re: /^(\d+)\s*([ap])(?:.?m.?)?$/i,
  1542. handler: function(bits) {
  1543. var hour = parseInt(bits[1]);
  1544. if (hour === 12) {
  1545. hour = 0;
  1546. }
  1547. if (bits[2].toLowerCase() === 'p') {
  1548. if (hour === 12) {
  1549. hour = 0;
  1550. }
  1551. return (hour + 12) + ':00';
  1552. } else {
  1553. if (hour < 10) {
  1554. return '0' + hour + ':00';
  1555. } else {
  1556. return hour + ':00';
  1557. }
  1558. }
  1559. }
  1560. },
  1561. // 3.30 am / 3:15 a.m. / 3.00am
  1562. {
  1563. re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i,
  1564. handler: function(bits) {
  1565. var hour = parseInt(bits[1]);
  1566. var mins = parseInt(bits[2]);
  1567. if (mins < 10) {
  1568. mins = '0' + mins;
  1569. }
  1570. if (hour === 12) {
  1571. hour = 0;
  1572. }
  1573. if (bits[3].toLowerCase() === 'p') {
  1574. if (hour === 12) {
  1575. hour = 0;
  1576. }
  1577. return (hour + 12) + ':' + mins;
  1578. } else {
  1579. if (hour < 10) {
  1580. return '0' + hour + ':' + mins;
  1581. } else {
  1582. return hour + ':' + mins;
  1583. }
  1584. }
  1585. }
  1586. },
  1587. // noon
  1588. {
  1589. re: /^no/i,
  1590. handler: function(bits) {
  1591. return '12:00';
  1592. }
  1593. },
  1594. // midnight
  1595. {
  1596. re: /^mid/i,
  1597. handler: function(bits) {
  1598. return '00:00';
  1599. }
  1600. }
  1601. ];
  1602.  
  1603. function parseTimeString(s) {
  1604. for (var i = 0; i < timeParsePatterns.length; i++) {
  1605. var re = timeParsePatterns[i].re;
  1606. var handler = timeParsePatterns[i].handler;
  1607. var bits = re.exec(s);
  1608. if (bits) {
  1609. return handler(bits);
  1610. }
  1611. }
  1612. return s;
  1613. }
  1614.  
  1615. window.parseTimeString = parseTimeString;
  1616. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement