Compare commits

..

83 Commits

Author SHA1 Message Date
aa51ce7861 Screw you codestyle 2021-01-29 18:45:16 +00:00
cdc8635e2f Add support for running tests with geckodriver, do this on CI 2021-01-29 18:43:34 +00:00
5d07a1853d Further template refactoring 2021-01-29 18:24:37 +00:00
1eed448828 Restore signals import, screw you import optimisation 2021-01-29 17:56:58 +00:00
e2e83e2cd8 Bunch of test refactoring 2021-01-29 16:39:38 +00:00
534e1000d4 Maybe this works to purge deps postbuild 2021-01-29 14:34:38 +00:00
84119f6685 Ignore test directories from Heroku slug 2021-01-29 14:24:29 +00:00
e50cf8d06c Add format arg to coverage command 2021-01-28 20:22:18 +00:00
a3970cf553 Revert "Minor python cleanup"
This reverts commit 6a4620a2e5.
2021-01-28 18:54:50 +00:00
4b8639faf4 Import optimisation 2021-01-28 18:51:22 +00:00
6a4620a2e5 Minor python cleanup 2021-01-28 18:38:55 +00:00
cc233e0223 Some template cleanup 2021-01-28 18:37:41 +00:00
2351201e13 Another go at making coverage show up 2021-01-28 18:05:57 +00:00
874b8ef37a Try ignoring some third party deprecation warnings 2021-01-28 18:05:46 +00:00
6a0b00dc76 Might fix actions? 2021-01-28 15:30:50 +00:00
1c31608951 Github, you need a Actions YAML validator! 2021-01-28 04:00:57 +00:00
37c0890a4b Belt and braces approach to coverage 2021-01-28 03:59:18 +00:00
c772744a4b Attempt #2 at fixing heroku 2021-01-28 03:44:09 +00:00
5319c2f6e3 Might fix heroku building 2021-01-28 03:33:06 +00:00
aa525372a2 Oops, again
Probably bedtime..
2021-01-28 02:49:55 +00:00
2774690b59 Attempt to bodge asset test 2021-01-28 02:45:04 +00:00
81733d32ba Fix codeclimate config, mark 2 2021-01-28 02:42:34 +00:00
e6527db6f7 Switch back to old coveralls method 2021-01-28 02:39:08 +00:00
d109ee231f Well the YAML was *syntactically* valid.... 2021-01-28 02:33:49 +00:00
cde46cc8e4 Update codeclimate settings, purge some config files 2021-01-28 02:32:38 +00:00
dddf0dc42a Parallel parallel builds were giving me a headache, try this 2021-01-28 02:30:00 +00:00
19c5f282d2 Does this work? 2021-01-28 02:17:40 +00:00
d5d2e9167c Exclude node_modules from codestyle 2021-01-28 02:10:36 +00:00
3b3ba0b87e Different approach to CI dependencies 2021-01-28 02:08:27 +00:00
1fbe59dfb0 See below 2021-01-28 01:50:48 +00:00
65c5307072 Still helps if I commit valid YAML 2021-01-28 01:50:04 +00:00
638816535e Run asset building serverside 2021-01-28 01:47:57 +00:00
8346d705ba Remove some unused gulp dependencies 2021-01-28 00:40:40 +00:00
18eed1a654 Add codeclimate maintainability badge 2021-01-27 23:49:36 +00:00
5174a442bc Try this way of parallel coverage 2021-01-27 20:21:29 +00:00
5f1fd59dd2 Fix screenshot uploading on CI (again) 2021-01-27 19:47:30 +00:00
3be2a9f4b5 Oops, remove obsolete if check 2021-01-27 19:39:24 +00:00
883ef4ed8b Bah, codestyle 2021-01-27 19:36:42 +00:00
2195a60438 Use pytest as our test runner for better parallelism
Also rewrote some asset tests to be in the pytest style. May do some more. Some warnings cleaned up in the process.
2021-01-27 19:33:20 +00:00
12c4b63947 Revert "Does this help the coverage be less weird?"
This reverts commit 39ab9df836.
2021-01-26 23:53:59 +00:00
39ab9df836 Does this help the coverage be less weird? 2021-01-26 23:36:26 +00:00
a99e4b1d1c What about this?
Swear I spend my life jiggerypokerying the damn test suite...
2021-01-26 21:51:59 +00:00
18a091f8ea What about this? 2021-01-26 19:42:54 +00:00
d64d0f54d4 Should fix asset test on CI 2021-01-26 19:37:12 +00:00
8f54897b69 Upload failure screenshots as individual artifacts not a zip
Turns out I can't unzip things from my phone, which is a pain
2021-01-26 19:31:06 +00:00
97830596b5 Fix unauth test to not just immediately pass out 2021-01-26 19:15:29 +00:00
68f401097d Add title checking to the slightly insane assets test 2021-01-26 17:43:01 +00:00
8c5b2f426d Refactor asset audit tests with better selectors
Also fixed a silly title error with the modal
2021-01-26 15:08:51 +00:00
e3fc05772e Exclude tests from the coverage stats
Seems to be artifically deflating our stats
2021-01-26 13:42:58 +00:00
e38205b9e7 Oops, remove unused import 2021-01-25 22:45:02 +00:00
f2b9642772 Switch to gh-a artifact uploading instead of imgur 'hack'
For test failure screenshots. Happy now @mattysmith22? ;p
2021-01-25 22:27:21 +00:00
08939a0e1f Purge old .idea config 2021-01-25 22:18:15 +00:00
15b7f9c7c1 No Ruby compass bodge, no need for rubocop! 2021-01-25 22:17:37 +00:00
c67f7e1e54 Purge old vagrant config 2021-01-25 22:16:25 +00:00
4f268b3168 Cache python dependencies
Should majorly speedup parallelillelelised testing
2021-01-25 22:08:32 +00:00
0e7723828a Update python version in tests 2021-01-25 21:59:23 +00:00
c46a2c53f3 Run parallelised RIGS tests as one matrix job 2021-01-25 21:55:32 +00:00
b3617a74bf Define service in coveralls task 2021-01-25 21:44:05 +00:00
5806cbc72d Change python ver 2021-01-25 21:42:55 +00:00
ab2ff9f146 Fix whoops in requirements.txt 2021-01-25 21:39:44 +00:00
fd5575a818 You valid now? 2021-01-25 21:37:22 +00:00
402a1dd7f0 Tends to help if I push valid yaml 2021-01-25 21:29:32 +00:00
be7688aa75 Upgrade python deps 2021-01-25 18:14:49 +00:00
a472e414f7 Add basic calendar button test
Mainly to pickup on FullCalendar loading errors
2021-01-25 18:09:21 +00:00
618c02aa9c Attempt at parallelising tests where possible 2021-01-25 16:42:12 +00:00
3056b6ef71 Fix audit time check in asset audit test 2021-01-25 16:07:44 +00:00
548c960996 npm upgrade 2021-01-25 15:50:36 +00:00
2cc43dcdb7 Move some gulp deps to dev rather than prod 2021-01-25 15:39:51 +00:00
6db25fb56b Upgrade to heroku-20 stack 2021-01-25 15:39:04 +00:00
8ad629a47e Replace Travis with Github Actions (#416)
Closes #415

Also enables coverage tracking of Django templates, hence the ~30% drop in coverage!
2021-01-25 01:20:12 +00:00
dependabot[bot]
2414eb9724 Build(deps): Bump lxml from 4.5.0 to 4.6.2 (#417)
Bumps [lxml](https://github.com/lxml/lxml) from 4.5.0 to 4.6.2.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.5.0...lxml-4.6.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-24 00:23:54 +00:00
3414204209 Refactor buildsystem to NPM/Gulp, port to BS4 & rewrite RIGS tests accordingly... (#412)
* Start to seperate versioning into its own app

* Start reworking invoice things

* Reduced overall font size a touch

* Improvements to generic lists

* Tweak some colours to be a bit less OTT

I need to work out if I can seperate background and primary colours like BS3 did

* Improvements to event table mobile

* First pass at mobile-ising the generic list

* Item table fixes

* Fixed fullcalendar print css not included

* Asset list table improvements

* Tweak asset list to be more in line with other lists

* Versioning template improvements

//TODO Rather than have seperate asset templates, convert 'id' into a template variable

* Tweak versioning templates to allow ID overrides

Asset specific templates begone. Still need to bring back the ID formatting for the Rigboard.

* Asset form fixes

* Use the right autocompleter.js...

* Breakout (most) user stuff to separate module

The model remains in RIGS for now, as it's pretty painful to move...

* Python Format/import opt

* Test Refactor Part 1 - Shuffle things around

* Fix migrations

TODO - need to ensure moved models are *moved* rather than deleted and recreated!

* Start on new tests

* Initial work on event create test reimpl

* Init other tests, more rigs test faffery

* Desaturate theme colors even more

Much closer to BS3

* Fix event item adding

Bit too heavy handed with the deduplication there Arona

* Initial refactor of event item testing

* Upgrade bootstrap-select

* Updated bootstrap-select for BS4

* Initial port of duplicate testing

Needs the latter half rewriting once we have an EventDetail POM

* Refactor date validation test

So close to killing test_functional.EventTest!

* Deduplication of testing code

* pep8

* Fix some tests

And some things that were actually borked

* FIX: Prevent setting access time after start time 

Cherry pick of d274ea4606. Will close #405.

* Refactor calendar tests

* FIX: Don't show asset buttons/history for basic users

* Really ought to get a pre-commit hook for pep8...

* Fully replace test_functional

* Dedupe generic search logic

* Fix the remaining tests

* Ensure submit button is scrolled to in tests

* Fix asset creation test + actually verify its results

* Make CI use latest (stable) chromedriver rather than some ancient one

Since Travis uses the latest stable chrome, should always match. Bash oneliner \o/

* Of course | is part of YAML syntax, of course...

Maybe this works.

* Update python version

Trying to get CI to match my local environment as much as possible...

* Minor test futzing

* Well that wasn't clever of me

* That was even less clever of me

* Revert to old submit wait behaviour

* What about if I did this

* Try disabling chrome cache

* Added screenshot recording of test failures

* Fixed RIGS tests not being run

* Fixed Pep8 - I promise I'll make a pre-commit hook sometime!

* Very initial work at togglable darktheme.

Dammit @alexdaniel654 just when I had my scope creep kinda under control. It'll be v. nice to have though...!

* More dark theme wangling

* Fix some asset template things

* FIX: CI Locale Issues

* Fix sample command

* Initial work at integrating the risk assessment

#136. No clever database structure as yet...

* FIX: Don't set every boolean input to radios

* Different approach to RA linking

* Move text definitions to somewhere more authoratitive

* FIX: Undo breakage causing autopep8

o.O

* Expand detail template

* Use correct view for RA history

* Initial work at coercing activity feed into showing RAs

Also shows Asset/Supplier on the homepage feed.

* Refactor activity feed template logic

Yay for removing arbitrary if/else chains!

* Initial work on caching activity feed

Server side that is. Ref #162.

* Start RA list template

* Refactor RA creation stuff, again

* Add H&S Details to Event Detail View

* Display venue notes in event detail

Notes are no use if nobody reads them. Not sure on this one.

* Add ability to filter event archive by status

Closes #168.

* Fix lingering naive time

* Use locmem cache in sqlite environments

Otherwise the tests just lock up totally. Should close #162

* Update dependencies

Mirrors/supersedes 0e67da82e2

* Add global ctrl/meta-enter shortcut for form submission

Wants rewriting for better efficiency, but hey, it works!

* Update dependencies

* Fix for a situation that should be impossible

* Fix navbar alignment

* FEAT: Improve 'omni'search

- Partialised template
- Added to assets header
- Added ability to search assets/suppliers
- Improved selection logic
- Have it display current query

* Move closemodal into PyRIGS

* Fix tests for search improvements

* Dark mode colour improvements

* Fix table colors for dry hires

* further darktheme fixes

* Remove the dark header from light theme

* Fix reload loops when CSS/JS is changed

* Move dark theme SCSS to separate file, fix inactive pagination styling

* Genercise detail pages

* Testing something re notes

I wonder if I can make that global, rather than per-template...

* Dark theme palette shenanigans

I just can't decide

* Match darktheme palette to forum darktheme palette

Why reinvent the wheel.

* Make supplier detail use the generic template

* Disable mobile event table PoC for now

* Remove the defaults from the RA fields + make them required

* More RA fixes

* Fixes to revisions for RAs

* Add bootstrap 4 test page

* Bunch of dark mode fixes from test page

* Do not use Django 'required' for radio selects

As this requires them to be True, whereas we just need to require that an option be entered.

* Properly fixed popover darktheme

* Fixed search for events

* Style fixes to asset list

* Start RA 'mark review' feature

* Add reviewing to revision history, fix RA editing not working

Also actually commit all the files, that helps

* Fix Power MIC being lost on RA edit

Why it is subtly different to the Event Update behaviour? Who knows

* Invalidate RA review if it is edited after review

* Start work on event checklist

* Add a button for creating and instantly voiding invoices

Handy dandy for when you have loads of cancelled events, like say, a pandemic

* Mooooore status chips, mooore

* Initial shenanigans on storing my overly fancy EC form

* Proof of concept for JSON parsing/storage

\o/

* Add new line functionality for vehicles/drivers

Might it have been easier to create 'dummy' models like with EventItems? Probably...

* Alter rig_count to not include un-checked-in dry hires

* Insert a divider between still-out dry hires and actually upcoming events on rigboard

* Initial work on new checklist handling. No more JSON!

* Versioning module now does magic

Automatic creation of views/urls for anything registered with reversion, with a small amount of hackage to preserve legacy stuff. (and the DAMNED asset IDs!) I would never get distracted...

* Cleanup

* Event checklist crew works

Mostly - its not happy with timezones

* Medium event power stuff done, barring worst case stuff

* Misc fixes

* Validation of power reqs

* Worst case points on checklist

* Templating improvements to RA/EC stuff

* Do event table color logic at python level

* Audit template fixes

* Restrict versioning to one level of depth for speed

Also fixed the template for nested changes

* Event properties internal/authorised always return a explicit boolean rather than sometimes None

* Use template filter for notes

* Fix list templates

TODO: Sensible place to define the 'expected answer' stuff.

* Fix cable table template

* Rethink rigboard color logic again

Also revert some broken stuff

* Test fixes

* Modify auth test so it doesn't try and test for external authorisations

Cause that's not a thing

* Why does this work

Bloody overzealous autoformatter...

* Formatting...

* Initial work on RA tests

* Pages/start of tests for EventChecklists

* Much better coverage of H&S things

* Cleanup & Squash migrations

* Fix wrong variable name in settings.py

* Fix broken invoice list template

* Add revision history to invoices/payments.

Also patches previously introduced reversion permissions hole.

Supersedes and closes #337.

* Various misc fixes

* Fix for my fix

* Curse youuuuu pep8

* Invoice template improvements

* Minor fixes

* More tweaks

* More fixes

* Major improvements/fixes to authorisation templates

* Add ability to mark event checklists as Large Event

This just disables the checks to allow the rest of it to be filled out for large events, though I expect paper forms may still be used...

* Remove database ID from generic list

* Put power threshold values in a collapse

* Use template filter for consistent removal of 'None links'

Plus cleaner template markup! More HTML-in-Python tho, which always feels a bit CSS-in-JS

* Tweak asset list markup

* Begin to change add buttons success -> primary

Also change search primary -> info to avoid clash

* Begin to improve event checklist on mobile

* Asset detail template improvements

* Fix #326 (again)

* Fix errors being squashed

* Fix rigboard validation tests

* Initial work on BS4 button templatetag

Newfeatureitis strikes again

* Allow multiple event checklists per event

TODO: Status chip now needs rethinking

* Minor event detail fixes

* Fix tests

* Rework button tag

* Mobile fixes for search

* Fix event checklist on mobile

* Redo light theme palette

* Switch rigboard new button to primary

* Kill off excess whitespace on rigboard

* Rigboard Timing display tweaks

* Fix tests

* Properly handle eventauthorisations in new versioning

It's not great, not terrible...

* Prevent creating duplicate revisions on event

Potential fix for #322 - I couldn't reproduce even before this change...

* Template improvements

* Minor test fixes

* Revert "Prevent creating duplicate revisions on event"

Apparently it was too strong at preventing dupes...

This reverts commit cce0ad0f9f.

# Conflicts:
#	RIGS/models.py

* Better approach to generic list templates + other deduplication

* Also apply better approach to generic detail pages

* One of these days I'll remember to test BEFORE pushing...

* And now the same for generic forms

* Display tick/cross rather than true/false in boolean version diffs

* Upgrade dependencies

* Fixes fixes fixes

* Fix dependency hell

Probably

* Correct handling of spaces in paperwork filenames

Also normalises display of Invoice IDs. Partial fix for #391.

* Buggerit millennium hand and shrimp

Knew I was gonna forget to fix the tests

* FIX: Set duplicated event status to provisional

Closes #398.

Flip flop. Flip flop.

* Update polyfill for datetime-local

Bloody Firefox. We love to hate you. Proper CSS of the fill to come, SoonTM.

Closes #391

* Curses!

* Minor typo fixes

* Initial pass at soop-consult confirmation screen for RAs

* Fix migration

* Make venue/date editable on EC

For multi venue, multi day events

Defaults to date and venue set on the event. Also made power MIC default to that set in RA

* Clearer logic for RA inverted fields

* (probably) fix tests

* Give keyholders supplier edit perm

* Generic list only displays edit button if user has perm

* Same perm check for generic details

* H&S Details takes up free space on non-internal events

* Remove flash of content when loading new rig page

* First pass at clearer display of asset list filters

* Fix tests / default to headless tests

(fingers crossed)

* Fix autocompleter.js to properly disable edit links again

* Move status color logic back to template

Cause that somehow makes it work better??

* Display note icon on event detail page

* Fix caching

* Put rounded corners back where they belong

* Remove lingering use of 'page-header'

BS removed that style

* More search and replace for BS changes

Thought I'd got them all. Clearly not!

* Remove enforced linebreak on status chips

* Fix horizontal-ness on some forms

* Remove animation on prefers-reduced-motion/low referesh rate devices

Also normalises handling of asset list cable table & improves its use of space on large devices

* Make version changes badges more readable

* First pass at making the calendar less crap

* Fix event table success logic

Yay for copy paste fails >.>

* Use borders rather than block colors for coloured tables under darktheme

* First pass at porting calendar from FC V3 to V5

Two major versions and all they did was rename a bunch of names...TWICE.

* Rework version name method to avoid blank names on eventchecklist vehicles/crew

* Fix cable test

* Made radio button focus much more obvious on dark theme

* Implement Jerb's wording changes

* Fix one test, break another...

* Fix recent change stream list mutation issue

* FIX: Do not naively cache event table

Not that easy, it turns out. Duh.

* FEAT: Implement #413 show associated assets on cable type detail pg

Closes #413

* Allow H&S for non-events

* Update emergency contact number

* Improvements to profile detail page

* Implement some of Jonny's suggested changes

TODO:
- Define event size at RA time, pass through to EC
- Have later power questions be context dependent

* Test fixes

* Add space for power/rigging plans to be linked to RAs

* Start move of event size logic to RA from Ec

* Javascript required shenanigans for RA power

* More moving of event size logic

* Fixing tests for new logic etc

* Why does this work

Indeed, it may not

* FIX: Stupid typo in versioning.py

* Further minor fixes to versioning

* Add icons to H&S menu items

* Should fix calendar breaking in production

* Small alignment fix in asset list

* Squash migrations

Co-authored-by: Matthew Smith <psyms13@nottingham.ac.uk>
2021-01-23 22:22:37 +00:00
099a184f2e Update emergency contact number 2020-11-13 09:24:39 +00:00
dependabot[bot]
0e67da82e2 Build(deps): Bump django from 3.0.3 to 3.0.7 (#411)
Bumps [django](https://github.com/django/django) from 3.0.3 to 3.0.7.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.0.3...3.0.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-05 18:11:21 +01:00
02e8e8aaf7 Fix risk assessment link 2020-05-11 23:22:11 +01:00
David Taylor
4acd9156d0 Switch to heroku-18 stack (#409)
cedar-14 has been deprectated: https://devcenter.heroku.com/changelog-items/1413
2020-04-15 12:53:44 +01:00
3be06a7b25 Create initial asset audit framework (#403)
* WIP: Basic work on audit

* WIP: Audit modal works

Need to get the ID search working.

* WIP: Javascript shenanigans for asset audit search

It's not clean but it works..

* Improve audit search bar

Optimise for APM!

* Filter asset audit list by never-audited

* Added cable functionality to audit form

Also improved styling

* FIX: Revert partialising of asset search

* Various UX Improvements

Also rearranged asset detail/edit to be more space efficient

* FIX: Remove assets from to-be-audited table when audited

Previously required a page reload

* Improve sample data generator

Does reversion properly and sets colours for asset statuses

* FIX: Gracefully handle 404s in audit search

* FEAT: Add buttons for some common defaults on audit form

TODO: Partialise those fragments and add them to the edit/create forms too.

* FIX: Fix asset sample data command when run alone

* FEAT: More handy buttons

* FIX: Stop quickbuttons being tab-selected

If someone's tabbing through, they won't be needing the buttons...

* FIX: Hide asset detail buttons for basic users

* FIX: Migrations

* Start tests for audit

* Some deduplication for testing code

* Improve asset audit testing

* Remember to test the tests Arona

* Potentially make modal tests more consistent

* FIX?: Up WebDriverWait timeout for modal tests

* FIX?: What about this way...

* Remake migrations

* Fix README badges to point to right branch

While I'm here eh :P

* Use aware time in audit

* Fix migrations again

* Fix for my fix...

* Modify audit exclusions to properly prevent data loss

* pep eiiiiiight
2020-04-14 21:11:09 +01:00
0fe7d55eab Fix for existing invalid cable types
Also hotfix against more in the future. Proper rework needed...This is why I should have waited for review...! Lesson learnt?
2020-04-13 16:33:57 +01:00
be4a7baf8e Remove obsolete 'next_scheduled_maint' from asset model
Should fix production data...
2020-04-13 16:14:06 +01:00
a0491891e9 Add 'CableTypes' (#406)
* Move relevant fields and create migration to autogen cable types

* CRUD and ordering

* FIX: Prevent creating duplicate cable types

* FIX: pep8/remove debug print

* FIX: Meta migrations... :>

* FIX: Update tests to match new UX

* Move cabletype menu links into 'Assets' dropdown

* Fix migration

* Specify version of reportlab

Should fix CI - looks like I went a bit too ham-handed in my requirements.txt purge last time...
2020-04-13 15:54:43 +01:00
02d40d1b39 FIX: Patch for choices being none
Honestly no idea if this is going to work, I can't reproduce the issue locally...
2020-03-07 16:47:30 +00:00
8568c591a9 Update Python Dependencies (#404)
* [requires.io] dependency update

* Server starts...

Various things are broken, but it runs!

* [requires.io] dependency update

* [requires.io] dependency update

* [requires.io] dependency update

* FIX: Broken migrations

* FIX: Update auth framework

* FIX: Correct static use in templates

* FIX: Fix supplier sort

* FIX: Remaining tests

* Revert "Disable password reset as temporary fix to vulnerability (#396)"

This reverts commit e0c6a56263.

# Conflicts:
#	RIGS/urls.py

* FIX: Fix broken newlining in PDFs

Introduced by a change in Django 2.1 'HTML rendered by form widgets no longer includes a closing slash on void elements, e.g. <br>. This is incompatible within XHTML, although some widgets already used aspects of HTML5 such as boolean attributes.'

* FIX: Fix some Django4 deprecation warnings

Why not...

* Refactor dependency file

Should now only include dependencies we actually use, not dependencies of dependencies and unused things

* Add newlines to the paperwork print test event

This will catch the error encountered in 79ec9214f9

* Swap to pycodestyle rather than pep8 in Travis

And eliminate W605 errors

* Bit too heavy handed with the dep purge there...

* Whoops, helps if one installs pycodestyle...

* FIX: Re-add overridden login view

* Better fix for previous commit

* FIX: Bloody smartquotes

Co-authored-by: requires.io <support@requires.io>
2020-03-07 16:21:48 +00:00
David Taylor
797ad778a9 Improve search logic and allow search of event archive (#248)
* Added search to person, venue, organisation and event archive

* Added search to invoice archive

* Added event search to homepage

* Tidy up event search logic and optimise

* Fixed merge issues

* Stopped 404 on failed search

* Set default ordering of people, organisations & venues to alphabetical (rather than order of addition to database)

* Added invoice search to home page (if you have permissions)

* Made invoice archive sort by reverse invoice date (rather than order added to database)

* Added search help page (very pretty)

* Made single search box for all search types

* FIX: Missing date field breaking archive view

* FEAT: Add omnisearch to header

Tis a bit broken on mobile at the moment...

* CHORE: Conform old code to pep8

* FIX: Select the event form, not the search one in tests!

* Revert "FEAT: Add omnisearch to header"

This reverts commit 6bcb242d6b because it caused MANY more problems than anticipated...

* FIX: Stop 404 on failed search, again

* FEAT: Basic testing of search

* Use a tooltip to help explain the UX

Obviously since it needs a tooltip it isn't brilliant UX but the best I can think of for now...

Co-authored-by: Tom Price <tom@codedinternet.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Arona Jones <aj@aronajones.com>
2020-02-29 11:57:33 +00:00
362 changed files with 28957 additions and 36088 deletions

View File

@@ -1,32 +1,16 @@
---
engines:
version: 2
plugins:
csslint:
enabled: true
duplication:
enabled: true
config:
languages:
- ruby
- javascript
- python
- php
eslint:
enabled: true
fixme:
enabled: true
radon:
enabled: true
rubocop:
enabled: true
ratings:
paths:
- "**.css"
- "**.inc"
- "**.js"
- "**.jsx"
- "**.module"
- "**.php"
- "**.py"
- "**.rb"
exclude_paths:
- config/

View File

@@ -1,6 +1,3 @@
[run]
source =
./
omit =
*/migrations/*
plugins = django_coverage_plugin
omit = *migrations*, *tests*

View File

@@ -1,2 +0,0 @@
--exclude-exts=.min.css
--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes

View File

@@ -1 +0,0 @@
**/*{.,-}min.js

213
.eslintrc
View File

@@ -1,213 +0,0 @@
ecmaFeatures:
modules: true
jsx: true
env:
amd: true
browser: true
es6: true
jquery: true
node: true
# http://eslint.org/docs/rules/
rules:
# Possible Errors
comma-dangle: [2, never]
no-cond-assign: 2
no-console: 0
no-constant-condition: 2
no-control-regex: 2
no-debugger: 2
no-dupe-args: 2
no-dupe-keys: 2
no-duplicate-case: 2
no-empty: 2
no-empty-character-class: 2
no-ex-assign: 2
no-extra-boolean-cast: 2
no-extra-parens: 0
no-extra-semi: 2
no-func-assign: 2
no-inner-declarations: [2, functions]
no-invalid-regexp: 2
no-irregular-whitespace: 2
no-negated-in-lhs: 2
no-obj-calls: 2
no-regex-spaces: 2
no-sparse-arrays: 2
no-unexpected-multiline: 2
no-unreachable: 2
use-isnan: 2
valid-jsdoc: 0
valid-typeof: 2
# Best Practices
accessor-pairs: 2
block-scoped-var: 0
complexity: [2, 6]
consistent-return: 0
curly: 0
default-case: 0
dot-location: 0
dot-notation: 0
eqeqeq: 2
guard-for-in: 2
no-alert: 2
no-caller: 2
no-case-declarations: 2
no-div-regex: 2
no-else-return: 0
no-empty-label: 2
no-empty-pattern: 2
no-eq-null: 2
no-eval: 2
no-extend-native: 2
no-extra-bind: 2
no-fallthrough: 2
no-floating-decimal: 0
no-implicit-coercion: 0
no-implied-eval: 2
no-invalid-this: 0
no-iterator: 2
no-labels: 0
no-lone-blocks: 2
no-loop-func: 2
no-magic-number: 0
no-multi-spaces: 0
no-multi-str: 0
no-native-reassign: 2
no-new-func: 2
no-new-wrappers: 2
no-new: 2
no-octal-escape: 2
no-octal: 2
no-proto: 2
no-redeclare: 2
no-return-assign: 2
no-script-url: 2
no-self-compare: 2
no-sequences: 0
no-throw-literal: 0
no-unused-expressions: 2
no-useless-call: 2
no-useless-concat: 2
no-void: 2
no-warning-comments: 0
no-with: 2
radix: 2
vars-on-top: 0
wrap-iife: 2
yoda: 0
# Strict
strict: 0
# Variables
init-declarations: 0
no-catch-shadow: 2
no-delete-var: 2
no-label-var: 2
no-shadow-restricted-names: 2
no-shadow: 0
no-undef-init: 2
no-undef: 0
no-undefined: 0
no-unused-vars: 0
no-use-before-define: 0
# Node.js and CommonJS
callback-return: 2
global-require: 2
handle-callback-err: 2
no-mixed-requires: 0
no-new-require: 0
no-path-concat: 2
no-process-exit: 2
no-restricted-modules: 0
no-sync: 0
# Stylistic Issues
array-bracket-spacing: 0
block-spacing: 0
brace-style: 0
camelcase: 0
comma-spacing: 0
comma-style: 0
computed-property-spacing: 0
consistent-this: 0
eol-last: 0
func-names: 0
func-style: 0
id-length: 0
id-match: 0
indent: 0
jsx-quotes: 0
key-spacing: 0
linebreak-style: 0
lines-around-comment: 0
max-depth: 0
max-len: 0
max-nested-callbacks: 0
max-params: 0
max-statements: [2, 30]
new-cap: 0
new-parens: 0
newline-after-var: 0
no-array-constructor: 0
no-bitwise: 0
no-continue: 0
no-inline-comments: 0
no-lonely-if: 0
no-mixed-spaces-and-tabs: 0
no-multiple-empty-lines: 0
no-negated-condition: 0
no-nested-ternary: 0
no-new-object: 0
no-plusplus: 0
no-restricted-syntax: 0
no-spaced-func: 0
no-ternary: 0
no-trailing-spaces: 0
no-underscore-dangle: 0
no-unneeded-ternary: 0
object-curly-spacing: 0
one-var: 0
operator-assignment: 0
operator-linebreak: 0
padded-blocks: 0
quote-props: 0
quotes: 0
require-jsdoc: 0
semi-spacing: 0
semi: 0
sort-vars: 0
space-after-keywords: 0
space-before-blocks: 0
space-before-function-paren: 0
space-before-keywords: 0
space-in-parens: 0
space-infix-ops: 0
space-return-throw-case: 0
space-unary-ops: 0
spaced-comment: 0
wrap-regex: 0
# ECMAScript 6
arrow-body-style: 0
arrow-parens: 0
arrow-spacing: 0
constructor-super: 0
generator-star-spacing: 0
no-arrow-condition: 0
no-class-assign: 0
no-const-assign: 0
no-dupe-class-members: 0
no-this-before-super: 0
no-var: 0
object-shorthand: 0
prefer-arrow-callback: 0
prefer-const: 0
prefer-reflect: 0
prefer-spread: 0
prefer-template: 0
require-yield: 0

63
.github/workflows/django.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Django CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
runs-on: ubuntu-latest
strategy:
matrix:
browser: ['chrome', 'firefox']
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BROWSER: ${{ matrix.browser }}
steps:
- uses: actions/checkout@v2
- uses: bahmutov/npm-install@v1
- run: node node_modules/gulp/bin/gulp build
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Cache python deps
uses: actions/cache@v2
with:
path: ${{ env.pythonLocation }}
key: ${{ env.pythonLocation }}-${{ hashFiles('requirements.txt') }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install pycodestyle coverage coveralls django_coverage_plugin pytest-cov
pip install --upgrade --upgrade-strategy eager -r requirements.txt
python manage.py collectstatic --noinput
- name: Cache gulp output
uses: actions/cache@v2
with:
path: static/
key: static-${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
- name: Basic Checks
run: |
pycodestyle . --exclude=migrations,node_modules
python manage.py check
python manage.py makemigrations --check --dry-run
- name: Run Tests
uses: paambaati/codeclimate-action@v2.7.5
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
with:
coverageCommand: coverage run -m pytest -n 8
coverageLocations: |
${{github.workspace}}/.coverage:coverage.py
- uses: actions/upload-artifact@v2
if: failure()
with:
name: failure-screenshots ${{ matrix.test-group }}
path: screenshots/
retention-days: 5
- name: Coveralls
run: coveralls --service=github

19
.gitignore vendored
View File

@@ -25,6 +25,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
node_modules/
# Continer extras
.vagrant
@@ -54,7 +55,6 @@ coverage.xml
# Django stuff:
*.log
db.sqlite3
# Sphinx documentation
docs/_build/
@@ -68,19 +68,9 @@ target/
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
#Built dependencies
pipeline/built_assets
# Gradle:
# .idea/gradle.xml
@@ -108,4 +98,5 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
.vscode/
.vscode/
screenshots/

1
.idea/.name generated
View File

@@ -1 +0,0 @@
PyRIGS

5
.idea/encodings.xml generated
View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

8
.idea/modules.xml generated
View File

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

View File

@@ -1,5 +0,0 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,3 @@
*.sqlite3
*.scss
*.md
*.rb
Vagrantfile
config/vagrant/*
config/vagrant.yml
**/tests

View File

@@ -1,32 +0,0 @@
language: python
python:
"3.6"
cache: pip
addons:
chrome: stable
install:
- wget https://chromedriver.storage.googleapis.com/2.36/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- export PATH=$PATH:$(pwd)
- chmod +x chromedriver
- pip install -r requirements.txt
- pip install coveralls codeclimate-test-reporter pep8
before_script:
- export PATH=$PATH:/usr/lib/chromium-browser/
- python manage.py collectstatic --noinput
script:
- pep8 . --exclude=migrations,importer*
- python manage.py check
- python manage.py makemigrations --check --dry-run
- coverage run manage.py test --verbosity=2
after_success:
- coveralls
- codeclimate-test-reporter
notifications:
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify

View File

@@ -1,6 +1,6 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from RIGS import models
@@ -8,7 +8,8 @@ from RIGS import models
def get_oembed(login_url, request, oembed_view, kwargs):
context = {}
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'],
reverse(oembed_view, kwargs=kwargs))
context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
resp = render(request, 'login_redirect.html', context=context)
return resp
@@ -28,9 +29,11 @@ def has_oembed(oembed_view, login_url=None):
return get_oembed(login_url, request, oembed_view, kwargs)
else:
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
_checklogin.__doc__ = view_func.__doc__
_checklogin.__dict__ = view_func.__dict__
return _checklogin
return _dec
@@ -60,9 +63,11 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
resp = render(request, '403.html')
resp.status_code = 403
return resp
_checklogin.__doc__ = view_func.__doc__
_checklogin.__dict__ = view_func.__dict__
return _checklogin
return _dec
@@ -80,6 +85,7 @@ def api_key_required(function):
Failed users will be given a 403 error.
Should only be used for urls which include <api_pk> and <api_key> kwargs
"""
def wrap(request, *args, **kwargs):
userid = kwargs.get('api_pk')
@@ -101,6 +107,7 @@ def api_key_required(function):
if user_object.api_key != key:
return error_resp
return function(request, *args, **kwargs)
return wrap
@@ -108,11 +115,13 @@ def nottinghamtec_address_required(function):
"""
Checks that the current user has an email address ending @nottinghamtec.co.uk
"""
def wrap(request, *args, **kwargs):
# Fail if current user's email address isn't @nottinghamtec.co.uk
if not request.user.email.endswith('@nottinghamtec.co.uk'):
error_resp = render(request, 'RIGS/eventauthorisation_request_error.html')
error_resp = render(request, 'eventauthorisation_request_error.html')
return error_resp
return function(request, *args, **kwargs)
return wrap

View File

@@ -1,5 +1,3 @@
DATETIME_FORMAT = ('d/m/Y H:i')
DATE_FORMAT = ('d/m/Y')
TIME_FORMAT = ('H:i')

View File

@@ -8,11 +8,13 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
import datetime
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import raven
import secrets
import datetime
import raven
from envparse import env
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -20,14 +22,15 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get(
'SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
SECRET_KEY = env('SECRET_KEY', default='gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True
DEBUG = env('DEBUG', cast=bool, default=True)
STAGING = env('STAGING', cast=bool, default=False)
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
CI = env('CI', cast=bool, default=False)
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
@@ -45,12 +48,12 @@ if not DEBUG:
INTERNAL_IPS = ['127.0.0.1']
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'), ('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'),
('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
if DEBUG:
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
@@ -58,6 +61,9 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'versioning',
'users',
'RIGS',
'assets',
@@ -148,11 +154,29 @@ LOGGING = {
}
}
# Tests lock up SQLite otherwise
if STAGING or CI:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
}
}
elif DEBUG:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
else:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'cache_table',
}
}
RAVEN_CONFIG = {
'dsn': os.environ.get('RAVEN_DSN'),
# If you are using git, you can also automatically configure the
# release based on the git info.
# 'release': raven.fetch_git_sha(os.path.dirname(os.path.dirname(__file__))),
'dsn': env('RAVEN_DSN', default=""),
}
# User system
@@ -165,21 +189,23 @@ LOGOUT_URL = '/user/logout/'
ACCOUNT_ACTIVATION_DAYS = 7
# reCAPTCHA settings
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
NOCAPTCHA = True
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
# Email
EMAILER_TEST = False
if not DEBUG or EMAILER_TEST:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25))
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0)))
EMAIL_USE_SSL = bool(int(os.environ.get('EMAIL_USE_SSL', 0)))
DEFAULT_FROM_EMAIL = os.environ.get('EMAIL_FROM')
EMAIL_HOST = env('EMAIL_HOST')
EMAIL_PORT = env('EMAIL_PORT', cast=int, default=25)
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = env('EMAIL_USE_TLS', cast=bool, default=False)
EMAIL_USE_SSL = env('EMAIL_USE_SSL', cast=bool, default=False)
DEFAULT_FROM_EMAIL = env('EMAIL_FROM')
else:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@@ -200,22 +226,25 @@ USE_L10N = True
USE_TZ = True
# Need to allow seconds as datetime-local input type spits out a time that has seconds
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
STATIC_DIRS = (
os.path.join(BASE_DIR, 'static/')
)
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'pipeline/built_assets/'),
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
os.path.join(BASE_DIR, 'templates')
],
'APP_DIRS': True,
'OPTIONS': {
@@ -238,7 +267,3 @@ USE_GRAVATAR = True
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get(
'RISK_ASSESSMENT_URL') else "http://example.com"
RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get(
'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15)

0
PyRIGS/tests/__init__.py Normal file
View File

View File

@@ -1,24 +1,44 @@
import os
import pathlib
import sys
from datetime import datetime
import pytz
from django.conf import settings
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from RIGS import models as rigsmodels
from . import pages
import os
from envparse import env
def create_browser():
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1920,1080")
if os.environ.get('CI', False):
def create_datetime(year, month, day, hour, min):
tz = pytz.timezone(settings.TIME_ZONE)
return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc)
def create_browser(browser):
if browser == "firefox":
options = webdriver.FirefoxOptions()
options.headless = True
driver = webdriver.Firefox(options=options)
else:
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1920,1080")
options.add_argument("--headless")
options.add_argument("--no-sandbox")
driver = webdriver.Chrome(chrome_options=options)
if settings.CI:
options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=options)
return driver
class BaseTest(LiveServerTestCase):
def setUp(self):
super().setUpClass()
self.driver = create_browser()
self.driver = create_browser(env('BROWSER', default="chrome"))
self.wait = WebDriverWait(self.driver, 15)
def tearDown(self):
super().tearDown()
@@ -32,5 +52,32 @@ class AutoLoginTest(BaseTest):
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
self.profile.set_password("EventTestPassword")
self.profile.save()
loginPage = pages.LoginPage(self.driver, self.live_server_url).open()
loginPage.login("EventTest", "EventTestPassword")
login_page = pages.LoginPage(self.driver, self.live_server_url).open()
login_page.login("EventTest", "EventTestPassword")
def screenshot_failure(func):
def wrapper_func(self, *args, **kwargs):
try:
func(self, *args, **kwargs)
except Exception as e:
screenshot_name = func.__module__ + "." + func.__qualname__
screenshot_file = "screenshots/" + func.__qualname__ + ".png"
if not pathlib.Path("screenshots").is_dir():
os.mkdir("screenshots")
self.driver.save_screenshot(screenshot_file)
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
raise e
return wrapper_func
def screenshot_failure_cls(cls):
for attr in cls.__dict__:
if callable(getattr(cls, attr)) and attr.startswith("test"):
setattr(cls, attr, screenshot_failure(getattr(cls, attr)))
return cls
def assert_times_equal(first_time, second_time):
assert first_time.replace(microsecond=0) == second_time.replace(microsecond=0)

View File

@@ -1,7 +1,9 @@
from pypom import Page, Region
from selenium.webdriver.common.by import By
from selenium.webdriver import Chrome
from pypom import Page
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from PyRIGS.tests import regions
class BasePage(Page):
@@ -29,42 +31,30 @@ class BasePage(Page):
class FormPage(BasePage):
_errors_selector = (By.CLASS_NAME, "alert-danger")
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
def remove_all_required(self):
self.driver.execute_script("Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
self.driver.execute_script("Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
self.driver.execute_script(
"Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
self.driver.execute_script(
"Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
def submit(self):
previous_errors = self.errors
submit = self.find_element(*self._submit_locator)
ActionChains(self.driver).move_to_element(submit).perform()
submit.click()
self.wait.until(animation_is_finished())
self.wait.until(lambda x: self.errors != previous_errors or self.success)
@property
def errors(self):
try:
error_page = self.ErrorPage(self, self.find_element(*self._errors_selector))
error_page = regions.ErrorPage(self, self.find_element(*self._errors_selector))
return error_page.errors
except NoSuchElementException:
return None
class ErrorPage(Region):
_error_item_selector = (By.CSS_SELECTOR, "dl>span")
class ErrorItem(Region):
_field_selector = (By.CSS_SELECTOR, "dt")
_error_selector = (By.CSS_SELECTOR, "dd>ul>li")
@property
def field_name(self):
return self.find_element(*self._field_selector).text
@property
def errors(self):
return [x.text for x in self.find_elements(*self._error_selector)]
@property
def errors(self):
error_items = [self.ErrorItem(self, x) for x in self.find_elements(*self._error_item_selector)]
errors = {}
for error in error_items:
errors[error.field_name] = error.errors
return errors
class LoginPage(BasePage):
URL_TEMPLATE = '/user/login'
@@ -84,3 +74,13 @@ class LoginPage(BasePage):
password_element.send_keys(password)
self.find_element(*self._submit_locator).click()
class animation_is_finished():
def __call__(self, driver):
number_animating = driver.execute_script('return $(":animated").length')
finished = number_animating == 0
if finished:
import time
time.sleep(0.1)
return finished

View File

@@ -1,11 +1,13 @@
from pypom import Region
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.select import Select
import datetime
from django.conf import settings
from pypom import Region
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.select import Select
def parse_bool_from_string(string):
# Used to convert from attribute strings to boolean values, written after I found this:
@@ -17,10 +19,25 @@ def parse_bool_from_string(string):
return False
def get_time_format():
# Default
time_format = "%H%M"
if settings.CI: # The CI is American
time_format = "%I%M%p"
return time_format
def get_date_format():
date_format = "%d%m%Y"
if settings.CI: # And try as I might I can't stop it being so
date_format = "%m%d%Y"
return date_format
class BootstrapSelectElement(Region):
_main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle')
_option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu')
_option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a[role=option]')
_option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a.dropdown-item')
_select_all_locator = (By.CLASS_NAME, 'bs-select-all')
_deselect_all_locator = (By.CLASS_NAME, 'bs-deselect-all')
_search_locator = (By.CSS_SELECTOR, '.bs-searchbox>input')
@@ -32,12 +49,12 @@ class BootstrapSelectElement(Region):
def toggle(self):
original_state = self.is_open
return self.find_element(*self._main_button_locator).click()
option_box = self.find_element(*self._option_box_locator)
if original_state:
self.wait.until(expected_conditions.invisibility_of_element_located(option_box))
if not original_state:
self.wait.until(expected_conditions.invisibility_of_element(option_box))
else:
self.wait.until(expected_conditions.visibility_of_element_located(option_box))
self.wait.until(expected_conditions.visibility_of(option_box))
return self.find_element(*self._main_button_locator).click()
def open(self):
if not self.is_open:
@@ -55,10 +72,10 @@ class BootstrapSelectElement(Region):
def search(self, query):
search_box = self.find_element(*self._search_locator)
self.open()
search_box.clear()
search_box.send_keys(query)
status_text = self.find_element(*self._status_locator)
self.wait.until(expected_conditions.invisibility_of_element_located(self._status_locator))
self.wait.until(expected_conditions.invisibility_of_element_located(*self._status_locator))
@property
def options(self):
@@ -112,6 +129,22 @@ class CheckBox(Region):
self.toggle()
class RadioSelect(Region): # Currently only works for yes/no radio selects
def set_value(self, value):
if value:
value = "0"
else:
value = "1"
self.find_element(By.XPATH, "//label[@for='{}_{}']".format(self.root.get_attribute("id"), value)).click()
@property
def value(self):
try:
return parse_bool_from_string(self.find_element(By.CSS_SELECTOR, '.custom-control-input:checked').get_attribute("value").lower())
except NoSuchElementException:
return None
class DatePicker(Region):
@property
def value(self):
@@ -119,7 +152,33 @@ class DatePicker(Region):
def set_value(self, value):
self.root.clear()
self.root.send_keys(value.strftime("%d%m%Y"))
self.root.send_keys(value.strftime(get_date_format()))
class TimePicker(Region):
@property
def value(self):
return datetime.datetime.strptime(self.root.get_attribute("value"), "%H:%M")
def set_value(self, value):
self.root.clear()
self.root.send_keys(value.strftime(get_time_format()))
class DateTimePicker(Region):
@property
def value(self):
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d %H:%M")
def set_value(self, value):
self.root.clear()
date = value.date().strftime(get_date_format())
time = value.time().strftime(get_time_format())
self.root.send_keys(date)
self.root.send_keys(Keys.TAB)
self.root.send_keys(time)
class SingleSelectPicker(Region):
@@ -131,3 +190,63 @@ class SingleSelectPicker(Region):
def set_value(self, value):
picker = Select(self.root)
picker.select_by_visible_text(value)
class ErrorPage(Region):
_error_item_selector = (By.CSS_SELECTOR, "dl>span")
class ErrorItem(Region):
_field_selector = (By.CSS_SELECTOR, "dt")
_error_selector = (By.CSS_SELECTOR, "dd>ul>li")
@property
def field_name(self):
return self.find_element(*self._field_selector).text
@property
def errors(self):
return [x.text for x in self.find_elements(*self._error_selector)]
@property
def errors(self):
error_items = [self.ErrorItem(self, x) for x in self.find_elements(*self._error_item_selector)]
errors = {}
for error in error_items:
errors[error.field_name] = error.errors
return errors
class Modal(Region):
_submit_locator = (By.CSS_SELECTOR, '.btn-primary')
_header_selector = (By.TAG_NAME, 'h4')
form_items = {
'name': (TextBox, (By.ID, 'id_name'))
}
@property
def header(self):
return self.find_element(*self._header_selector).text
@property
def is_open(self):
return self.root.is_displayed()
def submit(self):
self.root.find_element(*self._submit_locator).click()
def __getattr__(self, name):
if name in self.form_items:
element = self.form_items[name]
form_element = element[0](self, self.find_element(*element[1]))
return form_element.value
else:
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name in self.form_items:
element = self.form_items[name]
form_element = element[0](self, self.find_element(*element[1]))
form_element.set_value(value)
else:
self.__dict__[name] = value

View File

@@ -1,24 +1,32 @@
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.conf import settings
from registration.backends.default.views import RegistrationView
import RIGS
from RIGS import regbackend
from django.conf.urls import include
from django.contrib import admin
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import path, re_path
from django.views.generic import TemplateView
from PyRIGS import views
urlpatterns = [
# Examples:
# url(r'^$', 'PyRIGS.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
path('', include('versioning.urls')),
path('', include('RIGS.urls')),
path('assets/', include('assets.urls')),
url(r'^', include('RIGS.urls')),
url('^assets/', include('assets.urls')),
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
name="registration_register"),
url('^user/', include('django.contrib.auth.urls')),
url('^user/', include('registration.backends.default.urls')),
path('', login_required(views.Index.as_view()), name='index'),
url(r'^admin/', admin.site.urls),
# API
path('api/<str:model>/', login_required(views.SecureAPIRequest.as_view()),
name="api_secure"),
path('api/<str:model>/<int:pk>/', login_required(views.SecureAPIRequest.as_view()),
name="api_secure"),
path('closemodal/', views.CloseModal.as_view(), name='closemodal'),
path('search_help/', views.SearchHelp.as_view(), name='search_help'),
path('', include('users.urls')),
path('admin/', admin.site.urls),
]
if settings.DEBUG:
@@ -26,5 +34,6 @@ if settings.DEBUG:
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
re_path(r'^__debug__/', include(debug_toolbar.urls)),
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
] + urlpatterns

244
PyRIGS/views.py Normal file
View File

@@ -0,0 +1,244 @@
import datetime
import operator
from functools import reduce
import simplejson
from django.contrib import messages
from django.core import serializers
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy, reverse, NoReverseMatch
from django.views import generic
from RIGS import models
from assets import models as asset_models
def is_ajax(request):
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
# Displays the current rig count along with a few other bits and pieces
class Index(generic.TemplateView):
template_name = 'index.html'
def get_context_data(self, **kwargs):
context = super(Index, self).get_context_data(**kwargs)
context['rig_count'] = models.Event.objects.rig_count()
return context
class SecureAPIRequest(generic.View):
models = {
'venue': models.Venue,
'person': models.Person,
'organisation': models.Organisation,
'profile': models.Profile,
'event': models.Event,
'supplier': asset_models.Supplier
}
perms = {
'venue': 'RIGS.view_venue',
'person': 'RIGS.view_person',
'organisation': 'RIGS.view_organisation',
'profile': 'RIGS.view_profile',
'event': None,
'supplier': None
}
'''
Validate the request is allowed based on user permissions.
Raises 403 if denied.
Potential to add API key validation at a later date.
'''
def __validate__(self, request, key, perm):
if request.user.is_active:
if request.user.is_superuser or perm is None:
return True
elif request.user.has_perm(perm):
return True
raise PermissionDenied()
def get(self, request, model, pk=None, param=None):
# Request permission validation things
key = request.GET.get('apikey', None)
perm = self.perms[model]
self.__validate__(request, key, perm)
# Response format where applicable
format = request.GET.get('format', 'json')
fields = request.GET.get('fields', None)
if fields:
fields = fields.split(",")
# Supply data for one record
if pk:
object = get_object_or_404(self.models[model], pk=pk)
data = serializers.serialize(format, [object], fields=fields)
return HttpResponse(data, content_type="application/" + format)
# Supply data for autocomplete ajax request in json form
term = request.GET.get('q', None)
if term:
if fields is None: # Default to just name
fields = ['name']
# Build a list of Q objects for use later
queries = []
for part in term.split(" "):
qs = []
for field in fields:
q = Q(**{field + "__icontains": part})
qs.append(q)
queries.append(reduce(operator.or_, qs))
# Build the data response list
results = []
query = reduce(operator.and_, queries)
objects = self.models[model].objects.filter(query)
for o in objects:
data = {
'pk': o.pk,
'value': o.pk,
'text': o.name,
}
try: # See if there is a valid update URL
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
except NoReverseMatch:
pass
results.append(data)
# return a data response
json = simplejson.dumps(results)
return HttpResponse(json, content_type="application/json") # Always json
start = request.GET.get('start', None)
end = request.GET.get('end', None)
if model == "event" and start and end:
# Probably a calendar request
start_datetime = datetime.datetime.strptime(start, "%Y-%m-%dT%H:%M:%S")
end_datetime = datetime.datetime.strptime(end, "%Y-%m-%dT%H:%M:%S")
objects = self.models[model].objects.events_in_bounds(start_datetime, end_datetime)
results = []
for item in objects:
data = {
'pk': item.pk,
'title': item.name,
'is_rig': item.is_rig,
'status': str(item.get_status_display()),
'earliest': item.earliest_time.isoformat(),
'latest': item.latest_time.isoformat(),
'url': str(item.get_absolute_url())
}
results.append(data)
json = simplejson.dumps(results)
return HttpResponse(json, content_type="application/json") # Always json
return HttpResponse(model)
class ModalURLMixin:
def get_close_url(self, update, detail):
if is_ajax(self.request):
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk}))
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'")
else:
url = reverse_lazy(detail, kwargs={
'pk': self.object.pk,
})
return url
class GenericListView(generic.ListView):
template_name = 'generic_list.html'
paginate_by = 20
def get_context_data(self, **kwargs):
context = super(GenericListView, self).get_context_data(**kwargs)
context['page_title'] = self.model.__name__ + "s"
if is_ajax(self.request):
context['override'] = "base_ajax.html"
return context
def get_queryset(self):
q = self.request.GET.get('q', "")
filter = Q(name__icontains=q) | Q(email__icontains=q) | Q(address__icontains=q) | Q(notes__icontains=q) | Q(
phone__startswith=q) | Q(phone__endswith=q)
# try and parse an int
try:
val = int(q)
filter = filter | Q(pk=val)
except: # noqa
# not an integer
pass
object_list = self.model.objects.filter(filter)
orderBy = self.request.GET.get('orderBy', "name")
if orderBy != "":
object_list = object_list.order_by(orderBy)
return object_list
class GenericDetailView(generic.DetailView):
template_name = "generic_detail.html"
def get_context_data(self, **kwargs):
context = super(GenericDetailView, self).get_context_data(**kwargs)
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
if is_ajax(self.request):
context['override'] = "base_ajax.html"
return context
class GenericUpdateView(generic.UpdateView):
template_name = "generic_form.html"
def get_context_data(self, **kwargs):
context = super(GenericUpdateView, self).get_context_data(**kwargs)
context['page_title'] = "Edit {}".format(self.model.__name__)
if is_ajax(self.request):
context['override'] = "base_ajax.html"
return context
class GenericCreateView(generic.CreateView):
template_name = "generic_form.html"
def get_context_data(self, **kwargs):
context = super(GenericCreateView, self).get_context_data(**kwargs)
context['page_title'] = "Create {}".format(self.model.__name__)
if is_ajax(self.request):
context['override'] = "base_ajax.html"
return context
class SearchHelp(generic.TemplateView):
template_name = 'search_help.html'
"""
Called from a modal window (e.g. when an item is submitted to an event/invoice).
May optionally also include some javascript in a success message to cause a load of
the new information onto the page.
"""
class CloseModal(generic.TemplateView):
template_name = 'closemodal.html'
def get_context_data(self, **kwargs):
return {'messages': messages.get_messages(self.request)}

View File

@@ -7,6 +7,7 @@ For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyRIGS.settings")
from django.core.wsgi import get_wsgi_application # noqa

117
README.md
View File

@@ -1,111 +1,18 @@
# TEC PA & Lighting - PyRIGS #
[![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg?branch=develop)](https://travis-ci.org/nottinghamtec/PyRIGS)
[![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg?branch=develop)](https://coveralls.io/github/nottinghamtec/PyRIGS)
![Build Status](https://github.com/nottinghamtec/PyRIGS/workflows/Django%20CI/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg)](https://coveralls.io/github/nottinghamtec/PyRIGS)
[![Maintainability](https://api.codeclimate.com/v1/badges/79ca3b8106911a1d143f/maintainability)](https://codeclimate.com/github/nottinghamtec/PyRIGS/maintainability)
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
Welcome to TEC PA & Lighting's PyRIGS program. This is a reimplementation of the previous Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. PyRIGS is our in house app for the centralisation of information on our events and now assets.
The purpose of this project is to make the system more compatible and easier to understand such that should future changes be needed they can be made without having to understand the intricacies of Rails.
For setup information and other such helpful stuff check the [Wiki](https://github.com/nottinghamtec/PyRIGS/wiki)
### What is this repository for? ###
When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
Most of the documents here assume a basic knowledge of how Python and Django work (hint, if I don't say something, Google it, you will find 10000's of answers). The documentation is purely to be specific to TEC's application of the framework.
### Editing ###
It is recommended that you use the PyCharm IDE by JetBrains. Whilst other editors are available, this is the best for integration with Django as it can automatically manage all the pesky admin commands that frequently need running, as well as nice integration with git.
For the more experienced developer/somebody who doesn't want a full IDE and wants it to open in less than the age of the universe, I can strongly recommend [Sublime Text](http://www.sublimetext.com/). It has a bit of a steeper learning curve, and won't manage anything Django/git related out of the box, but once you get the hang of it is by far the fastest and most powerful editor I have used (for any type of project).
Please contact TJP for details on how to acquire these.
### Python Environment ###
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Orginally written with the C implementation of Python 2 (CPython 2, specifically the Python 2.7 standard), the application now runs in Python 3.
Once you have your Python distribution installed, go ahead an follow the steps to set up a virtualenv, which will isolate the project from the system environment.
#### PyCharm ####
If you are using the prefered PyCharm IDE, then this should be quite easy.
1. Select "File/Settings" -> "Project Interpreter"
2. Click the small cog in the top right
3. Select "Create VirtualEnv"
4. Enter a name and a location. This doesn't matter where, just make sure it makes sense and you remember it incase you need it later (I recommend calling it "pyrigs" in "~/.virtualenvs/pyrigs")
5. Select the base interpreter to your Python 3 base interpreter (Python 2 will work, just be careful)
6. Click OK, you *don't* want to inherit global packages or make it available to all projects.
7. Open a file such as manage.py. PyCharm should winge that dependances aren't installed. This might take a while to register, but give it change. When it does, click the button to install them and let it do it's thing. If for some reason PyCharm should decide that it doesn't want to help you here, see below for the console instructions on how to do this manually.
To run the Django application follow these steps
1. Select "Run/Edit Configurations"
2. Create a new "Django server", give it a sensible name for when you need it later.
3. You might need to set the interpreter to be your virtualenv.
4. Click "OK"
5. Run the application
#### Console Based ####
If you aren't using PyCharm, or want to use a console for some reason, this is really easy, there is even [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to help things along. Simply run
```
virtualenv <dir>
```
Where dir is the directory you wish to create the virtualenv in.
Next activate the virtualenv.
```
Windows
<virtualenv_dir>/Scripts/activate.bat
Unix
source <virtualenv_dir>/bin/activate
```
Finally install the requirements using pip
```
cd <pyrigs project directory>
pip install -r requirements.txt
```
This might take a while, but be patient and you should then be ready to go.
To run the server under normal conditions when you are already in the virtualenv (see above)
```
python manage.py runserver
```
Please refer to Django documentation for a full list of options available here.
### Development using docker
```
docker build . -t pyrigs
docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs
```
### Sample Data ###
Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
```
python manage.py flush
```
Then load the sample data using the command:
```
python manage.py generateSampleData
```
4 user accounts are created for convenience:
|Username |Password |
|---------|---------|
|superuser|superuser|
|finance |finance |
|keyholder|keyholder|
|basic |basic |
### Testing ###
Tests are contained in 3 files. `RIGS/test_models.py` contains tests for logic within the data models. `RIGS/test_unit.py` contains "Live server" tests, using raw web requests. `RIGS/test_integration.py` contains user interface tests which take control of a web browser. For automated Travis tests, we use [Sauce Labs](https://saucelabs.com). When debugging locally, ensure that you have the latest version of Google Chrome installed, then install [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) and ensure it is on the `PATH`.
You can run the entire test suite, or you can run specific sections individually. For example, in order of specificity:
```
python manage.py test
python manage.py test RIGS.test_models
python manage.py test RIGS.test_models.EventTestCase
python manage.py test RIGS.test_models.EventTestCase.test_current_events
```
# Apps
- PyRIGS: Base app, stores 'global' information
- RIGS: Rigboard stuff - event calendar etc
- assets: Database of our kit, testing data etc
- versioning: Our custom logic built on top of django-reversion. Semi-modular.
- users: Our custom logic for registration and profiles. Semi-modular.
- training: SoonTM
[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://forthebadge.com)

View File

@@ -1,25 +1,24 @@
from django.contrib import admin
from RIGS import models, forms
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from reversion.admin import VersionAdmin
from django.contrib.admin import helpers
from django.template.response import TemplateResponse
from django.contrib import messages
from django.db import transaction
from django.contrib.admin import helpers
from django.contrib.auth.admin import UserAdmin
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models import Count
from django.forms import ModelForm
from django.template.response import TemplateResponse
from django.utils.translation import gettext_lazy as _
from reversion import revisions as reversion
from reversion.admin import VersionAdmin
from RIGS import models
from users import forms as user_forms
# Register your models here.
admin.site.register(models.VatRate, VersionAdmin)
admin.site.register(models.Event, VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin)
admin.site.register(models.Invoice)
admin.site.register(models.Payment)
admin.site.register(models.Invoice, VersionAdmin)
def approve_user(modeladmin, request, queryset):
@@ -48,8 +47,8 @@ class ProfileAdmin(UserAdmin):
'fields': ('username', 'password1', 'password2'),
}),
)
form = forms.ProfileChangeForm
add_form = forms.ProfileCreationForm
form = user_forms.ProfileChangeForm
add_form = user_forms.ProfileCreationForm
actions = [approve_user]
@@ -105,7 +104,7 @@ class AssociateAdmin(VersionAdmin):
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'forms': forms
}
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context)
return TemplateResponse(request, 'admin_associate_merge.html', context)
@admin.register(models.Person)
@@ -124,3 +123,13 @@ class VenueAdmin(AssociateAdmin):
class OrganisationAdmin(AssociateAdmin):
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'union_account']
@admin.register(models.RiskAssessment)
class RiskAssessmentAdmin(VersionAdmin):
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
@admin.register(models.EventChecklist)
class EventChecklistAdmin(VersionAdmin):
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')

View File

@@ -1,34 +1,35 @@
import datetime
import re
import reversion
from django import forms
from django.contrib import messages
from django.urls import reverse_lazy
from django.db import transaction
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.template.loader import get_template
from django.urls import reverse_lazy
from django.views import generic
from django.db.models import Q
from z3c.rml import rml2pdf
from RIGS import models
from django import forms
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
class InvoiceIndex(generic.ListView):
model = models.Invoice
template_name = 'RIGS/invoice_list_active.html'
template_name = 'invoice_list.html'
def get_context_data(self, **kwargs):
context = super(InvoiceIndex, self).get_context_data(**kwargs)
total = 0
for i in context['object_list']:
total += i.balance
context['total'] = total
context['count'] = len(list(context['object_list']))
context['page_title'] = "Outstanding Invoices ({} Events, £{:.2f})".format(len(list(context['object_list'])), total)
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
return context
def get_queryset(self):
@@ -50,13 +51,19 @@ class InvoiceIndex(generic.ListView):
class InvoiceDetail(generic.DetailView):
model = models.Invoice
template_name = 'invoice_detail.html'
def get_context_data(self, **kwargs):
context = super(InvoiceDetail, self).get_context_data(**kwargs)
context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
return context
class InvoicePrint(generic.View):
def get(self, request, pk):
invoice = get_object_or_404(models.Invoice, pk=pk)
object = invoice.event
template = get_template('RIGS/event_print.xml')
template = get_template('event_print.xml')
context = {
'object': object,
@@ -68,6 +75,7 @@ class InvoicePrint(generic.View):
},
'invoice': invoice,
'current_user': request.user,
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
}
rml = template.render(context)
@@ -76,10 +84,8 @@ class InvoicePrint(generic.View):
pdfData = buffer.read()
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = "filename=Invoice %05d - N%05d | %s.pdf" % (invoice.pk, invoice.event.pk, escapedEventName)
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
response.write(pdfData)
return response
@@ -98,6 +104,7 @@ class InvoiceVoid(generic.View):
class InvoiceDelete(generic.DeleteView):
model = models.Invoice
template_name = 'invoice_confirm_delete.html'
def get(self, request, pk):
obj = self.get_object()
@@ -119,22 +126,55 @@ class InvoiceDelete(generic.DeleteView):
class InvoiceArchive(generic.ListView):
model = models.Invoice
template_name = 'RIGS/invoice_list_archive.html'
template_name = 'invoice_list_archive.html'
paginate_by = 25
def get_context_data(self, **kwargs):
context = super(InvoiceArchive, self).get_context_data(**kwargs)
context['page_title'] = "Invoice Archive"
context['description'] = "This page displays all invoices: outstanding, paid, and void"
return context
def get_queryset(self):
q = self.request.GET.get('q', "")
filter = Q(event__name__icontains=q)
# try and parse an int
try:
val = int(q)
filter = filter | Q(pk=val)
filter = filter | Q(event__pk=val)
except: # noqa
# not an integer
pass
try:
if q[0] == "N":
val = int(q[1:])
filter = Q(event__pk=val) # If string is Nxxxxx then filter by event number
elif q[0] == "#":
val = int(q[1:])
filter = Q(pk=val) # If string is #xxxxx then filter by invoice number
except: # noqa
pass
object_list = self.model.objects.filter(filter).order_by('-invoice_date')
return object_list
class InvoiceWaiting(generic.ListView):
model = models.Event
paginate_by = 25
template_name = 'RIGS/event_invoice.html'
template_name = 'invoice_list_waiting.html'
def get_context_data(self, **kwargs):
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
total = 0
for obj in self.get_objects():
total += obj.sum_total
context['total'] = total
context['count'] = len(self.get_objects())
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
return context
def get_queryset(self):
@@ -159,7 +199,10 @@ class InvoiceWaiting(generic.ListView):
class InvoiceEvent(generic.View):
@transaction.atomic()
@reversion.create_revision()
def get(self, *args, **kwargs):
reversion.set_user(self.request.user)
epk = kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
invoice, created = models.Invoice.objects.get_or_create(event=event)
@@ -168,12 +211,18 @@ class InvoiceEvent(generic.View):
invoice.invoice_date = datetime.date.today()
messages.success(self.request, 'Invoice created successfully')
if kwargs.get('void'):
invoice.void = not invoice.void
invoice.save()
messages.warning(self.request, 'Invoice voided')
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
class PaymentCreate(generic.CreateView):
model = models.Payment
fields = ['invoice', 'date', 'amount', 'method']
template_name = 'payment_form.html'
def get_initial(self):
initial = super(generic.CreateView, self).get_initial()
@@ -184,6 +233,13 @@ class PaymentCreate(generic.CreateView):
initial.update({'invoice': invoice})
return initial
@transaction.atomic()
@reversion.create_revision()
def form_valid(self, form, *args, **kwargs):
reversion.add_to_revision(form.cleaned_data['invoice'])
reversion.set_comment("Payment added")
return super().form_valid(form, *args, **kwargs)
def get_success_url(self):
messages.info(self.request, "location.reload()")
return reverse_lazy('closemodal')
@@ -191,6 +247,14 @@ class PaymentCreate(generic.CreateView):
class PaymentDelete(generic.DeleteView):
model = models.Payment
template_name = 'payment_confirm_delete.html'
@transaction.atomic()
@reversion.create_revision()
def delete(self, *args, **kwargs):
reversion.add_to_revision(self.get_object().invoice)
reversion.set_comment("Payment removed")
return super().delete(*args, **kwargs)
def get_success_url(self):
return self.request.POST.get('next')

View File

@@ -1,72 +1,23 @@
from datetime import datetime
import simplejson
from django import forms
from django.utils import formats
from django.conf import settings
from django.core import serializers
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm
from registration.forms import RegistrationFormUniqueEmail
from django.contrib.auth.forms import AuthenticationForm
from captcha.fields import ReCaptchaField
import simplejson
from django.utils import timezone
from reversion import revisions as reversion
from RIGS import models
# Override the django form defaults to use the HTML date/time/datetime UI elements
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
forms.TimeField.widget = forms.TextInput(attrs={'type': 'time'})
forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'})
# Registration
class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
captcha = ReCaptchaField()
class Meta:
model = models.Profile
fields = ('username', 'email', 'first_name', 'last_name', 'initials')
def clean_initials(self):
"""
Validate that the supplied initials are unique.
"""
if models.Profile.objects.filter(initials__iexact=self.cleaned_data['initials']):
raise forms.ValidationError("These initials are already in use. Please supply different initials.")
return self.cleaned_data['initials']
class CheckApprovedForm(AuthenticationForm):
def confirm_login_allowed(self, user):
if user.is_approved or user.is_superuser:
return AuthenticationForm.confirm_login_allowed(self, user)
else:
raise forms.ValidationError("Your account hasn't been approved by an administrator yet. Please check back in a few minutes!")
# Embedded Login form - remove the autofocus
class EmbeddedAuthenticationForm(CheckApprovedForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.pop('autofocus', None)
class PasswordReset(PasswordResetForm):
captcha = ReCaptchaField(label='Captcha')
class ProfileCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = models.Profile
class ProfileChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = models.Profile
forms.TimeField.widget = forms.TimeInput(attrs={'type': 'time'}, format='%H:%M')
forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M')
# Events Shit
class EventForm(forms.ModelForm):
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + list(settings.DATETIME_INPUT_FORMATS)
datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS)
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
@@ -140,8 +91,11 @@ class EventForm(forms.ModelForm):
return item
def clean(self):
if self.cleaned_data.get("is_rig") and not (self.cleaned_data.get('person') or self.cleaned_data.get('organisation')):
raise forms.ValidationError('You haven\'t provided any client contact details. Please add a person or organisation.', code='contact')
if self.cleaned_data.get("is_rig") and not (
self.cleaned_data.get('person') or self.cleaned_data.get('organisation')):
raise forms.ValidationError(
'You haven\'t provided any client contact details. Please add a person or organisation.',
code='contact')
return super(EventForm, self).clean()
def save(self, commit=True):
@@ -195,3 +149,129 @@ class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
class EventAuthorisationRequestForm(forms.Form):
email = forms.EmailField(required=True, label='Authoriser Email')
class EventRiskAssessmentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EventRiskAssessmentForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
if str(name) == 'supervisor_consulted':
field.widget = forms.CheckboxInput()
elif field.__class__ == forms.BooleanField:
field.widget = forms.RadioSelect(choices=[
(True, 'Yes'),
(False, 'No')
], attrs={'class': 'custom-control-input', 'required': 'true'})
def clean(self):
# Check expected values
unexpected_values = []
for field, value in models.RiskAssessment.expected_values.items():
if self.cleaned_data.get(field) != value:
unexpected_values.append("<li>{}</li>".format(self._meta.model._meta.get_field(field).help_text))
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
raise forms.ValidationError("Your answers to these questions: <ul>{}</ul> require consulting with a supervisor.".format(''.join([str(elem) for elem in unexpected_values])), code='unusual_answers')
return super(EventRiskAssessmentForm, self).clean()
class Meta:
model = models.RiskAssessment
fields = '__all__'
exclude = ['reviewed_at', 'reviewed_by']
class EventChecklistForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EventChecklistForm, self).__init__(*args, **kwargs)
self.fields['date'].widget.format = '%Y-%m-%d'
for name, field in self.fields.items():
if field.__class__ == forms.NullBooleanField:
# Only display yes/no to user, the 'none' is only ever set in the background
field.widget = forms.CheckboxInput()
# Parsed from incoming form data by clean, then saved into models when the form is saved
items = {}
related_models = {
'venue': models.Venue,
'power_mic': models.Profile,
}
# Two possible formats
def parsedatetime(self, date_string):
try:
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S'))
except ValueError:
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M'))
# There's probably a thousand better ways to do this, but this one is mine
def clean(self):
vehicles = {key: val for key, val in self.data.items()
if key.startswith('vehicle')}
for key in vehicles:
pk = int(key.split('_')[1])
driver_key = 'driver_' + str(pk)
if(self.data[driver_key] == ''):
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
else:
try:
item = models.EventChecklistVehicle.objects.get(pk=pk)
except models.EventChecklistVehicle.DoesNotExist:
item = models.EventChecklistVehicle()
item.vehicle = vehicles['vehicle_' + str(pk)]
item.driver = models.Profile.objects.get(pk=self.data[driver_key])
item.full_clean('checklist')
# item does not have a database pk yet as it isn't saved
self.items['v' + str(pk)] = item
crewmembers = {key: val for key, val in self.data.items()
if key.startswith('crewmember')}
other_fields = ['start', 'role', 'end']
for key in crewmembers:
pk = int(key.split('_')[1])
for field in other_fields:
value = self.data['{}_{}'.format(field, pk)]
if value == '':
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
try:
item = models.EventChecklistCrew.objects.get(pk=pk)
except models.EventChecklistCrew.DoesNotExist:
item = models.EventChecklistCrew()
item.crewmember = models.Profile.objects.get(pk=self.data['crewmember_' + str(pk)])
item.start = self.parsedatetime(self.data['start_' + str(pk)])
item.role = self.data['role_' + str(pk)]
item.end = self.parsedatetime(self.data['end_' + str(pk)])
item.full_clean('checklist')
# item does not have a database pk yet as it isn't saved
self.items['c' + str(pk)] = item
return super(EventChecklistForm, self).clean()
def save(self, commit=True):
checklist = super(EventChecklistForm, self).save(commit=False)
if (commit):
# Remove all existing, to be recreated from the form
checklist.vehicles.all().delete()
checklist.crew.all().delete()
checklist.save()
for key in self.items:
item = self.items[key]
reversion.add_to_revision(item)
# finish and save new database items
item.checklist = checklist
item.full_clean()
item.save()
self.items.clear()
return checklist
class Meta:
model = models.EventChecklist
fields = '__all__'
exclude = ['reviewed_at', 'reviewed_by']

217
RIGS/hs.py Normal file
View File

@@ -0,0 +1,217 @@
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils import timezone
from django.views import generic
from reversion import revisions as reversion
from RIGS import models, forms
class EventRiskAssessmentCreate(generic.CreateView):
model = models.RiskAssessment
template_name = 'risk_assessment_form.html'
form_class = forms.EventRiskAssessmentForm
def get(self, *args, **kwargs):
epk = kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
# Check if RA exists
ra = models.RiskAssessment.objects.filter(event=event).first()
if ra is not None:
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
return super(EventRiskAssessmentCreate, self).get(self)
def get_form(self, **kwargs):
form = super(EventRiskAssessmentCreate, self).get_form(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
form.instance.event = event
return form
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentCreate, self).get_context_data(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
context['event'] = event
context['page_title'] = 'Create Risk Assessment for Event {}'.format(event.display_id)
return context
def get_success_url(self):
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
class EventRiskAssessmentEdit(generic.UpdateView):
model = models.RiskAssessment
template_name = 'risk_assessment_form.html'
form_class = forms.EventRiskAssessmentForm
def get_success_url(self):
ra = self.get_object()
ra.reviewed_by = None
ra.reviewed_at = None
ra.save()
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
rpk = self.kwargs.get('pk')
ra = models.RiskAssessment.objects.get(pk=rpk)
context['event'] = ra.event
context['edit'] = True
context['page_title'] = 'Edit Risk Assessment for Event {}'.format(ra.event.display_id)
return context
class EventRiskAssessmentDetail(generic.DetailView):
model = models.RiskAssessment
template_name = 'risk_assessment_detail.html'
class EventRiskAssessmentList(generic.ListView):
paginate_by = 20
model = models.RiskAssessment
template_name = 'hs_object_list.html'
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
context['title'] = 'Risk Assessment'
context['view'] = 'ra_detail'
context['edit'] = 'ra_edit'
context['review'] = 'ra_review'
context['perm'] = 'perms.RIGS.review_riskassessment'
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
return context
class EventRiskAssessmentReview(generic.View):
def get(self, *args, **kwargs):
rpk = kwargs.get('pk')
ra = models.RiskAssessment.objects.get(pk=rpk)
with reversion.create_revision():
reversion.set_user(self.request.user)
ra.reviewed_by = self.request.user
ra.reviewed_at = timezone.now()
ra.save()
return HttpResponseRedirect(reverse_lazy('ra_list'))
class EventChecklistDetail(generic.DetailView):
model = models.EventChecklist
template_name = 'event_checklist_detail.html'
def get_context_data(self, **kwargs):
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
context['page_title'] = "Event Checklist for Event {} {}".format(self.object.event.display_id, self.object.event.name)
return context
class EventChecklistEdit(generic.UpdateView):
model = models.EventChecklist
template_name = 'event_checklist_form.html'
form_class = forms.EventChecklistForm
def get_success_url(self):
ec = self.get_object()
ec.reviewed_by = None
ec.reviewed_at = None
ec.save()
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
pk = self.kwargs.get('pk')
ec = models.EventChecklist.objects.get(pk=pk)
context['event'] = ec.event
context['edit'] = True
context['page_title'] = 'Edit Event Checklist for Event {}'.format(ec.event.display_id)
form = context['form']
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.items():
value = form[field].value()
if value is not None and value != '':
context[field] = model.objects.get(pk=value)
return context
class EventChecklistCreate(generic.CreateView):
model = models.EventChecklist
template_name = 'event_checklist_form.html'
form_class = forms.EventChecklistForm
# From both business logic and programming POVs, RAs must exist before ECs!
def get(self, *args, **kwargs):
epk = kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
# Check if RA exists
ra = models.RiskAssessment.objects.filter(event=event).first()
if ra is None:
messages.error(self.request, 'A Risk Assessment must exist prior to creating any Event Checklists for {}! Please create one now.'.format(event))
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
return super(EventChecklistCreate, self).get(self)
def get_form(self, **kwargs):
form = super(EventChecklistCreate, self).get_form(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
form.instance.event = event
return form
def get_context_data(self, **kwargs):
context = super(EventChecklistCreate, self).get_context_data(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
context['event'] = event
context['page_title'] = 'Create Event Checklist for Event {}'.format(event.display_id)
return context
def get_success_url(self):
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
class EventChecklistList(generic.ListView):
paginate_by = 20
model = models.EventChecklist
template_name = 'hs_object_list.html'
def get_context_data(self, **kwargs):
context = super(EventChecklistList, self).get_context_data(**kwargs)
context['title'] = 'Event Checklist'
context['view'] = 'ec_detail'
context['edit'] = 'ec_edit'
context['review'] = 'ec_review'
context['perm'] = 'perms.RIGS.review_eventchecklist'
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
return context
class EventChecklistReview(generic.View):
def get(self, *args, **kwargs):
rpk = kwargs.get('pk')
ec = models.EventChecklist.objects.get(pk=rpk)
with reversion.create_revision():
reversion.set_user(self.request.user)
ec.reviewed_by = self.request.user
ec.reviewed_at = timezone.now()
ec.save()
return HttpResponseRedirect(reverse_lazy('ec_list'))
class HSList(generic.ListView):
paginate_by = 20
model = models.Event
template_name = 'hs_list.html'
def get_queryset(self):
return models.Event.objects.all().order_by('-start_date')
def get_context_data(self, **kwargs):
context = super(HSList, self).get_context_data(**kwargs)
context['page_title'] = 'H&S Overview'
return context

View File

@@ -1,12 +1,11 @@
from RIGS import models, forms
from django_ical.views import ICalFeed
from django.db.models import Q
from django.urls import reverse_lazy, reverse, NoReverseMatch
from django.utils import timezone
from django.conf import settings
import datetime
import pytz
from django.conf import settings
from django.db.models import Q
from django_ical.views import ICalFeed
from RIGS import models
class CalendarICS(ICalFeed):
@@ -40,8 +39,10 @@ class CalendarICS(ICalFeed):
return params
def description(self, params):
desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + ('Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n'
desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + ('Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '')
desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + (
'Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n'
desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + (
'Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '')
return desc
@@ -72,7 +73,8 @@ class CalendarICS(ICalFeed):
filter = filter & typeFilters & statusFilters
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation',
'venue', 'mic')
def item_title(self, item):
title = ''
@@ -99,7 +101,7 @@ class CalendarICS(ICalFeed):
return item.earliest_time
def item_end_datetime(self, item):
if type(item.latest_time) == datetime.date: # Ical end_datetime is non-inclusive, so add a day
if isinstance(item.latest_time, datetime.date): # Ical end_datetime is non-inclusive, so add a day
return item.latest_time + datetime.timedelta(days=1)
return item.latest_time
@@ -117,19 +119,24 @@ class CalendarICS(ICalFeed):
desc += 'Event = ' + item.name + '\n'
desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n'
if item.is_rig and item.person:
desc += 'Client = ' + item.person.name + ((' for ' + item.organisation.name) if item.organisation else '') + '\n'
desc += 'Client = ' + item.person.name + (
(' for ' + item.organisation.name) if item.organisation else '') + '\n'
desc += 'Status = ' + str(item.get_status_display()) + '\n'
desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n'
desc += '\n'
if item.meet_at:
desc += 'Crew Meet = ' + (item.meet_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
desc += 'Crew Meet = ' + (
item.meet_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
if item.access_at:
desc += 'Access At = ' + (item.access_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.access_at else '---') + '\n'
desc += 'Access At = ' + (
item.access_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.access_at else '---') + '\n'
if item.start_date:
desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' ' + item.start_time.strftime('%H:%M')) if item.has_start_time else '') + '\n'
desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + (
(' ' + item.start_time.strftime('%H:%M')) if item.has_start_time else '') + '\n'
if item.end_date:
desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' ' + item.end_time.strftime('%H:%M')) if item.has_end_time else '') + '\n'
desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + (
(' ' + item.end_time.strftime('%H:%M')) if item.has_end_time else '') + '\n'
desc += '\n'
if item.description:

View File

@@ -1,28 +0,0 @@
__author__ = 'ghost'
import unittest
from importer import fix_email
class EmailFixerTest(unittest.TestCase):
def test_correct(self):
e = fix_email("tom@ghost.uk.net")
self.assertEqual(e, "tom@ghost.uk.net")
def test_partial(self):
e = fix_email("psytp")
self.assertEqual(e, "psytp@nottingham.ac.uk")
def test_none(self):
old = None
new = fix_email(old)
self.assertEqual(old, new)
def test_empty(self):
old = ""
new = fix_email(old)
self.assertEqual(old, new)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,5 +1,5 @@
from django.core.management.base import BaseCommand, CommandError
from django.core.management import call_command
from django.core.management.base import BaseCommand
class Command(BaseCommand):

View File

@@ -1,11 +1,11 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group, Permission
from django.db import transaction
from reversion import revisions as reversion
import datetime
import random
from django.contrib.auth.models import Group, Permission
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from reversion import revisions as reversion
from RIGS import models
@@ -20,6 +20,7 @@ class Command(BaseCommand):
keyholder_group = None
finance_group = None
hs_group = None
def handle(self, *args, **options):
from django.conf import settings
@@ -27,7 +28,8 @@ class Command(BaseCommand):
if not (settings.DEBUG or settings.STAGING):
raise CommandError('You cannot run this command in production')
random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests
random.seed(
'Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests
with transaction.atomic():
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
@@ -45,8 +47,18 @@ class Command(BaseCommand):
self.setupUsefulProfiles()
def setupPeople(self):
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", "Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore", "Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan", "Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid", "Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom", "Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa
"Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
"Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid",
"Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom",
"Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa
"Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime",
"Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter",
"Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter",
"Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley",
"Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley",
"Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
for i, name in enumerate(names):
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
@@ -68,8 +80,32 @@ class Command(BaseCommand):
self.people.append(newPerson)
def setupOrganisations(self):
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
"Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
"Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp",
"North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation",
"Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp",
"Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power",
"Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank",
"Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium",
"Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors",
"Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation",
"U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation",
"Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
"Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron",
"Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry",
"Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.",
"Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer",
"Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron",
"Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company",
"Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer",
"Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp",
"The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute",
"Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King",
"Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman",
"Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande",
"Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
for i, name in enumerate(names):
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
@@ -93,8 +129,18 @@ class Command(BaseCommand):
self.organisations.append(newOrganisation)
def setupVenues(self):
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch", "The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands", "The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins", "The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark", "Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye", "The Golden Tooth", # noqa
"Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis", "Yunkai"] # noqa
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
"The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark",
"Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye",
"The Golden Tooth", # noqa
"Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown",
"Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep",
"Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai",
"Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth",
"Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis",
"Yunkai"] # noqa
for i, name in enumerate(names):
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
@@ -120,6 +166,7 @@ class Command(BaseCommand):
def setupGroups(self):
self.keyholder_group = Group.objects.create(name='Keyholders')
self.finance_group = Group.objects.create(name='Finance')
self.hs_group = Group.objects.create(name='H&S')
keyholderPerms = ["add_event", "change_event", "view_event",
"add_eventitem", "change_eventitem", "delete_eventitem",
@@ -127,10 +174,17 @@ class Command(BaseCommand):
"add_person", "change_person", "view_person", "view_profile",
"add_venue", "change_venue", "view_venue",
"add_asset", "change_asset", "delete_asset",
"asset_finance", "view_asset", "view_supplier", "asset_finance",
"add_supplier"]
"view_asset", "view_supplier", "change_supplier", "asset_finance",
"add_supplier", "view_cabletype", "change_cabletype",
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
"change_eventchecklistvehicle",
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
"add_payment", "change_payment", "delete_payment"]
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
for permId in keyholderPerms:
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
@@ -138,10 +192,15 @@ class Command(BaseCommand):
for permId in financePerms:
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
for permId in hsPerms:
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
def setupGenericProfiles(self):
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble", "Jack Harkness", "Mickey Smith", "Rose Tyler"]
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
for i, name in enumerate(names):
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1],
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0],
last_name=name.split(" ")[-1],
email=name.replace(" ", "") + "@example.com",
initials="".join([j[0].upper() for j in name.split()]))
if i % 2 == 0:
@@ -151,44 +210,68 @@ class Command(BaseCommand):
self.profiles.append(newProfile)
def setupUsefulProfiles(self):
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", initials="SU",
email="superuser@example.com", is_superuser=True, is_active=True, is_staff=True)
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User",
initials="SU",
email="superuser@example.com", is_superuser=True, is_active=True,
is_staff=True)
superUser.set_password('superuser')
superUser.save()
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU",
email="financeuser@example.com", is_active=True)
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
initials="FU",
email="financeuser@example.com", is_active=True, is_approved=True)
financeUser.groups.add(self.finance_group)
financeUser.groups.add(self.keyholder_group)
financeUser.set_password('finance')
financeUser.save()
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU",
email="keyholderuser@example.com", is_active=True)
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
initials="HSU",
email="hsuser@example.com", is_active=True, is_approved=True)
hsUser.groups.add(self.hs_group)
hsUser.groups.add(self.keyholder_group)
hsUser.set_password('hs')
hsUser.save()
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
initials="KU",
email="keyholderuser@example.com", is_active=True, is_approved=True)
keyholderUser.groups.add(self.keyholder_group)
keyholderUser.set_password('keyholder')
keyholderUser.save()
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
email="basicuser@example.com", is_active=True)
email="basicuser@example.com", is_active=True, is_approved=True)
basicUser.set_password('basic')
basicUser.save()
def setupEvents(self):
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball", "Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show", "Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"]
descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen", "Warning: this has lots of kit"]
notes = ["The client came into the office at some point", "Who knows if this will happen", "Probably should check this event", "Maybe not happening", "Run away!"]
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show",
"Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection",
"Choir Concert"]
descriptions = ["A brief description of the event", "This event is boring", "Probably wont happen",
"Warning: this has lots of kit"]
notes = ["The client came into the office at some point", "Who knows if this will happen",
"Probably should check this event", "Maybe not happening", "Run away!"]
itemOptions = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00},
{'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00},
{'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52},
{'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00},
{'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50},
{'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00},
{'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00},
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
itemOptions = [
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
'cost': 200.00},
{'name': 'Projector',
'description': 'Some kind of video thinamejig, probably with unnecessary processing for free',
'quantity': 1, 'cost': 500.00},
{'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1,
'cost': 200.52},
{'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00},
{'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5,
'cost': 0.50},
{'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1,
'cost': 100.00},
{'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00},
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
dayDelta = -120 # start adding events from 4 months ago
@@ -226,7 +309,8 @@ class Command(BaseCommand):
newEvent.venue = random.choice(self.venues)
# Could have any status, equally weighted
newEvent.status = random.choice([models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
newEvent.status = random.choice(
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
@@ -257,4 +341,5 @@ class Command(BaseCommand):
if newEvent.status is models.Event.CANCELLED: # void cancelled events
newInvoice.void = True
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today())
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance,
date=datetime.date.today())

View File

@@ -0,0 +1,37 @@
# Generated by Django 2.0.13 on 2020-03-06 20:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0037_approve_legacy'),
]
operations = [
migrations.AlterModelOptions(
name='event',
options={},
),
migrations.AlterModelOptions(
name='invoice',
options={'ordering': ['-invoice_date']},
),
migrations.AlterModelOptions(
name='organisation',
options={},
),
migrations.AlterModelOptions(
name='person',
options={},
),
migrations.AlterModelOptions(
name='profile',
options={'verbose_name': 'user', 'verbose_name_plural': 'users'},
),
migrations.AlterModelOptions(
name='venue',
options={},
),
]

View File

@@ -0,0 +1,190 @@
# Generated by Django 3.1.2 on 2021-01-23 19:10
import RIGS.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0038_auto_20200306_2000'),
]
operations = [
migrations.CreateModel(
name='EventChecklist',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField()),
('safe_parking', models.BooleanField(blank=True, help_text='Vehicles parked safely?<br><small>(does not obstruct venue access)</small>', null=True)),
('safe_packing', models.BooleanField(blank=True, help_text='Equipment packed away safely?<br><small>(including flightcases)</small>', null=True)),
('exits', models.BooleanField(blank=True, help_text='Emergency exits clear?', null=True)),
('trip_hazard', models.BooleanField(blank=True, help_text='Appropriate barriers around kit and cabling secured?', null=True)),
('warning_signs', models.BooleanField(blank=True, help_text='Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>')),
('ear_plugs', models.BooleanField(blank=True, help_text='Ear plugs issued to crew where needed?', null=True)),
('hs_location', models.CharField(blank=True, help_text='Location of Safety Bag/Box', max_length=255, null=True)),
('extinguishers_location', models.CharField(blank=True, help_text='Location of fire extinguishers', max_length=255, null=True)),
('rcds', models.BooleanField(blank=True, help_text='RCDs installed where needed and tested?', null=True)),
('supply_test', models.BooleanField(blank=True, help_text='Electrical supplies tested?<br><small>(using socket tester)</small>', null=True)),
('earthing', models.BooleanField(blank=True, help_text='Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>', null=True)),
('pat', models.BooleanField(blank=True, help_text='All equipment in PAT period?', null=True)),
('source_rcd', models.BooleanField(blank=True, help_text='Source RCD protected?<br><small>(if cable is more than 3m long) </small>', null=True)),
('labelling', models.BooleanField(blank=True, help_text='Appropriate and clear labelling on distribution and cabling?', null=True)),
('fd_voltage_l1', models.IntegerField(blank=True, help_text='L1 - N', null=True, verbose_name='First Distro Voltage L1-N')),
('fd_voltage_l2', models.IntegerField(blank=True, help_text='L2 - N', null=True, verbose_name='First Distro Voltage L2-N')),
('fd_voltage_l3', models.IntegerField(blank=True, help_text='L3 - N', null=True, verbose_name='First Distro Voltage L3-N')),
('fd_phase_rotation', models.BooleanField(blank=True, help_text='Phase Rotation<br><small>(if required)</small>', null=True, verbose_name='Phase Rotation')),
('fd_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True, verbose_name='Earth Fault Loop Impedance')),
('fd_pssc', models.IntegerField(blank=True, help_text='Prospective Short Circuit Current', null=True, verbose_name='PSCC')),
('w1_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
('w1_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
('w1_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
('w1_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
('w2_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
('w2_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
('w2_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
('w2_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
('w3_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
('w3_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
('w3_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
('w3_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
('all_rcds_tested', models.BooleanField(blank=True, help_text='All circuit RCDs tested?<br><small>(using test button)</small>', null=True)),
('public_sockets_tested', models.BooleanField(blank=True, help_text='Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>', null=True)),
('reviewed_at', models.DateTimeField(null=True)),
],
options={
'ordering': ['event'],
'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='EventChecklistCrew',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(max_length=255)),
('start', models.DateTimeField()),
('end', models.DateTimeField()),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
],
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='EventChecklistVehicle',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vehicle', models.CharField(max_length=255)),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
],
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='RiskAssessment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nonstandard_equipment', models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>TEC's standard risk assessments and method statements?</a>")),
('nonstandard_use', models.BooleanField(help_text='Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>')),
('contractors', models.BooleanField(help_text='Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>')),
('other_companies', models.BooleanField(help_text='Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>')),
('crew_fatigue', models.BooleanField(help_text='Is crew fatigue likely to be a risk at any point during this event?')),
('general_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
('big_power', models.BooleanField(help_text='Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?')),
('outside', models.BooleanField(help_text='Is the event outdoors?')),
('generators', models.BooleanField(help_text='Will generators be used?')),
('other_companies_power', models.BooleanField(help_text='Will TEC be supplying power to any other companies?')),
('nonstandard_equipment_power', models.BooleanField(help_text='Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?')),
('multiple_electrical_environments', models.BooleanField(help_text='Will the electrical installation occupy more than one electrical environment?')),
('power_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
('power_plan', models.URLField(blank=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
('noise_monitoring', models.BooleanField(help_text='Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?')),
('sound_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
('known_venue', models.BooleanField(help_text='Is this venue new to you (the MIC) or new to TEC?')),
('safe_loading', models.BooleanField(help_text='Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)')),
('safe_storage', models.BooleanField(help_text='Are there any problems with safe and secure equipment storage?')),
('area_outside_of_control', models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")),
('barrier_required', models.BooleanField(help_text='Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?')),
('nonstandard_emergency_procedure', models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")),
('special_structures', models.BooleanField(help_text='Does the event require use of winch stands, motors, MPT Towers, or staging?')),
('suspended_structures', models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")),
('persons_responsible_structures', models.TextField(blank=True, help_text='Who are the persons on site responsible for their use?', null=True)),
('rigging_plan', models.URLField(blank=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
('reviewed_at', models.DateTimeField(null=True)),
('supervisor_consulted', models.BooleanField(null=True)),
],
options={
'ordering': ['event'],
'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.RemoveField(
model_name='eventcrew',
name='event',
),
migrations.RemoveField(
model_name='eventcrew',
name='user',
),
migrations.DeleteModel(
name='RIGSVersion',
),
migrations.RemoveField(
model_name='event',
name='risk_assessment_edit_url',
),
migrations.AlterField(
model_name='profile',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
migrations.DeleteModel(
name='EventCrew',
),
migrations.AddField(
model_name='riskassessment',
name='event',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='RIGS.event'),
),
migrations.AddField(
model_name='riskassessment',
name='power_mic',
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='power_mic', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
),
migrations.AddField(
model_name='riskassessment',
name='reviewed_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
),
migrations.AddField(
model_name='eventchecklistvehicle',
name='driver',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='eventchecklistcrew',
name='crewmember',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crewed', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='eventchecklist',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to='RIGS.event'),
),
migrations.AddField(
model_name='eventchecklist',
name='power_mic',
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC?', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
),
migrations.AddField(
model_name='eventchecklist',
name='reviewed_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
),
migrations.AddField(
model_name='eventchecklist',
name='venue',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='RIGS.venue'),
),
]

View File

@@ -1,34 +1,31 @@
import datetime
import hashlib
import datetime
import pytz
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.encoding import python_2_unicode_compatible
from reversion import revisions as reversion
from reversion.models import Version
import string
import random
import string
from collections import Counter
from decimal import Decimal
from urllib.parse import urlparse
import pytz
from django import forms
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.functional import cached_property
from reversion import revisions as reversion
from reversion.models import Version
# Create your models here.
@python_2_unicode_compatible
class Profile(AbstractUser):
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, null=True, blank=True)
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
is_approved = models.BooleanField(default=False)
last_emailed = models.DateTimeField(blank=True, null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
last_emailed = models.DateTimeField(blank=True,
null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
@classmethod
def make_api_key(cls):
@@ -41,7 +38,8 @@ class Profile(AbstractUser):
def profile_picture(self):
url = ""
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email.encode('utf-8')).hexdigest() + "?d=wavatar&s=500"
url = "https://www.gravatar.com/avatar/" + hashlib.md5(
self.email.encode('utf-8')).hexdigest() + "?d=wavatar&s=500"
return url
@property
@@ -66,13 +64,15 @@ class Profile(AbstractUser):
def __str__(self):
return self.name
class Meta:
permissions = (
('view_profile', 'Can view Profile'),
)
# TODO move to versioning - currently get import errors with that
class RevisionMixin(object):
@property
def is_first_version(self):
versions = Version.objects.get_for_object(self)
return len(versions) == 1
@property
def current_version(self):
version = Version.objects.get_for_object(self).select_related('revision').first()
@@ -100,8 +100,6 @@ class RevisionMixin(object):
return "V{0} | R{1}".format(version.pk, version.revision.pk)
@reversion.register
@python_2_unicode_compatible
class Person(models.Model, RevisionMixin):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=15, blank=True, null=True)
@@ -137,14 +135,7 @@ class Person(models.Model, RevisionMixin):
def get_absolute_url(self):
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
class Meta:
permissions = (
('view_person', 'Can view Persons'),
)
@reversion.register
@python_2_unicode_compatible
class Organisation(models.Model, RevisionMixin):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=15, blank=True, null=True)
@@ -181,11 +172,6 @@ class Organisation(models.Model, RevisionMixin):
def get_absolute_url(self):
return reverse_lazy('organisation_detail', kwargs={'pk': self.pk})
class Meta:
permissions = (
('view_organisation', 'Can view Organisations'),
)
class VatManager(models.Manager):
def current_rate(self):
@@ -202,7 +188,6 @@ class VatManager(models.Manager):
@reversion.register
@python_2_unicode_compatible
class VatRate(models.Model, RevisionMixin):
start_at = models.DateField()
rate = models.DecimalField(max_digits=6, decimal_places=6)
@@ -210,6 +195,8 @@ class VatRate(models.Model, RevisionMixin):
objects = VatManager()
reversion_hide = True
@property
def as_percent(self):
return self.rate * 100
@@ -222,8 +209,6 @@ class VatRate(models.Model, RevisionMixin):
return self.comment + " " + str(self.start_at) + " @ " + str(self.as_percent) + "%"
@reversion.register
@python_2_unicode_compatible
class Venue(models.Model, RevisionMixin):
name = models.CharField(max_length=255)
phone = models.CharField(max_length=15, blank=True, null=True)
@@ -246,21 +231,22 @@ class Venue(models.Model, RevisionMixin):
def get_absolute_url(self):
return reverse_lazy('venue_detail', kwargs={'pk': self.pk})
class Meta:
permissions = (
('view_venue', 'Can view Venues'),
)
class EventManager(models.Manager):
def current_events(self):
events = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(
status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
'organisation',
'venue', 'mic')
return events
def events_in_bounds(self, start, end):
@@ -289,15 +275,12 @@ class EventManager(models.Manager):
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT
status=Event.CANCELLED)) # Active dry hire
).count()
return event_count
@reversion.register(follow=['items'])
@python_2_unicode_compatible
class Event(models.Model, RevisionMixin):
# Done to make it much nicer on the database
PROVISIONAL = 0
@@ -333,7 +316,8 @@ class Event(models.Model, RevisionMixin):
meet_info = models.CharField(max_length=255, blank=True, null=True)
# Crew management
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True, on_delete=models.CASCADE)
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
on_delete=models.CASCADE)
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True,
verbose_name="MIC", on_delete=models.CASCADE)
@@ -348,8 +332,12 @@ class Event(models.Model, RevisionMixin):
auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(null=True, blank=True)
# Risk assessment info
risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True)
@property
def display_id(self):
if self.is_rig:
return str("N%05d" % self.pk)
else:
return self.pk
# Calculated values
"""
@@ -358,17 +346,6 @@ class Event(models.Model, RevisionMixin):
@property
def sum_total(self):
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
# if connection.vendor == 'postgresql':
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
# else:
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
# total = self.items.raw(sql)[0]
# if total.sum_total:
# return total.sum_total
# total = 0.0
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
# total += item.sum
total = EventItem.objects.filter(event=self).aggregate(
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
output_field=models.DecimalField(max_digits=10, decimal_places=2))
@@ -402,8 +379,8 @@ class Event(models.Model, RevisionMixin):
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
@property
def authorised(self):
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
def hs_done(self):
return self.riskassessment is not None and len(self.checklists.all()) > 0
@property
def has_start_time(self):
@@ -467,7 +444,14 @@ class Event(models.Model, RevisionMixin):
@property
def internal(self):
return self.organisation and self.organisation.union_account
return bool(self.organisation and self.organisation.union_account)
@property
def authorised(self):
if self.internal:
return self.authorisation.amount == self.total
else:
return bool(self.purchase_order)
objects = EventManager()
@@ -475,29 +459,35 @@ class Event(models.Model, RevisionMixin):
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
def __str__(self):
return str(self.pk) + ": " + self.name
return "{}: {}".format(self.display_id, self.name)
def clean(self):
errdict = {}
if self.end_date and self.start_date > self.end_date:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
errdict['end_date'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
startEndSameDay = not self.end_date or self.end_date == self.start_date
hasStartAndEnd = self.has_start_time and self.has_end_time
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
if self.access_at is not None:
if self.access_at.date() > self.start_date:
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
elif self.start_time is not None and self.start_date == self.access_at.date() and self.access_at.time() > self.start_time:
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
if errdict != {}: # If there was an error when validation
raise ValidationError(errdict)
def save(self, *args, **kwargs):
"""Call :meth:`full_clean` before saving."""
self.full_clean()
super(Event, self).save(*args, **kwargs)
class Meta:
permissions = (
('view_event', 'Can view Events'),
)
class EventItem(models.Model):
@reversion.register
class EventItem(models.Model, RevisionMixin):
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
@@ -505,6 +495,8 @@ class EventItem(models.Model):
cost = models.DecimalField(max_digits=10, decimal_places=2)
order = models.IntegerField()
reversion_hide = True
@property
def total_cost(self):
return self.cost * self.quantity
@@ -515,14 +507,9 @@ class EventItem(models.Model):
def __str__(self):
return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
class EventCrew(models.Model):
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
rig = models.BooleanField(default=False)
run = models.BooleanField(default=False)
derig = models.BooleanField(default=False)
notes = models.TextField(blank=True, null=True)
@property
def activity_feed_string(self):
return str("item {}".format(self.name))
@reversion.register
@@ -533,22 +520,24 @@ class EventAuthorisation(models.Model, RevisionMixin):
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
account_code = models.CharField(max_length=50, blank=True, null=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
sent_by = models.ForeignKey('RIGS.Profile', on_delete=models.CASCADE)
sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
@property
def activity_feed_string(self):
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
@python_2_unicode_compatible
class Invoice(models.Model):
@reversion.register(follow=['payment_set'])
class Invoice(models.Model, RevisionMixin):
event = models.OneToOneField('Event', on_delete=models.CASCADE)
invoice_date = models.DateField(auto_now_add=True)
void = models.BooleanField(default=False)
reversion_perm = 'RIGS.view_invoice'
@property
def sum_total(self):
return self.event.sum_total
@@ -572,18 +561,26 @@ class Invoice(models.Model):
def is_closed(self):
return self.balance == 0 or self.void
def get_absolute_url(self):
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
@property
def activity_feed_string(self):
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
def __str__(self):
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
@property
def display_id(self):
return "{:05d}".format(self.pk)
class Meta:
permissions = (
('view_invoice', 'Can view Invoices'),
)
ordering = ['-invoice_date']
@python_2_unicode_compatible
class Payment(models.Model):
@reversion.register
class Payment(models.Model, RevisionMixin):
CASH = 'C'
INTERNAL = 'I'
EXTERNAL = 'E'
@@ -602,5 +599,239 @@ class Payment(models.Model):
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
reversion_hide = True
def __str__(self):
return "%s: %d" % (self.get_method_display(), self.amount)
@property
def activity_feed_string(self):
return str("payment of £{}".format(self.amount))
def validate_url(value):
if not value:
return # Required error is done the field
obj = urlparse(value)
if obj.hostname not in ('nottinghamtec.sharepoint.com'):
raise ValidationError('URL must point to a location on the TEC Sharepoint')
@reversion.register
class RiskAssessment(models.Model, RevisionMixin):
SMALL = (0, 'Small')
MEDIUM = (1, 'Medium')
LARGE = (2, 'Large')
SIZES = (SMALL, MEDIUM, LARGE)
event = models.OneToOneField('Event', on_delete=models.CASCADE)
# General
nonstandard_equipment = models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>"
"TEC's standard risk assessments and method statements?</a>")
nonstandard_use = models.BooleanField(help_text="Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>")
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
# Power
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
# If yes to the above two, you must answer...
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
outside = models.BooleanField(help_text="Is the event outdoors?")
generators = models.BooleanField(help_text="Will generators be used?")
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Sound
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
# Site
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
safe_loading = models.BooleanField(help_text="Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)")
safe_storage = models.BooleanField(help_text="Are there any problems with safe and secure equipment storage?")
area_outside_of_control = models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")
barrier_required = models.BooleanField(help_text="Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?")
nonstandard_emergency_procedure = models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")
# Structures
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Blimey that was a lot of options
reviewed_at = models.DateTimeField(null=True)
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name="Reviewer", on_delete=models.CASCADE)
supervisor_consulted = models.BooleanField(null=True)
expected_values = {
'nonstandard_equipment': False,
'nonstandard_use': False,
'contractors': False,
'other_companies': False,
'crew_fatigue': False,
'big_power': False,
'generators': False,
'other_companies_power': False,
'nonstandard_equipment_power': False,
'multiple_electrical_environments': False,
'noise_monitoring': False,
'known_venue': False,
'safe_loading': False,
'safe_storage': False,
'area_outside_of_control': False,
'barrier_required': False,
'nonstandard_emergency_procedure': False,
'special_structures': False,
'suspended_structures': False,
}
inverted_fields = {key: value for (key, value) in expected_values.items() if not value}.keys()
def clean(self):
# Check for idiots
if not self.outside and self.generators:
raise forms.ValidationError("Engage brain, please. <strong>No generators indoors!(!)</strong>")
class Meta:
ordering = ['event']
permissions = [
('review_riskassessment', 'Can review Risk Assessments')
]
@property
def event_size(self):
# Confirm event size. Check all except generators, since generators entails outside
if self.outside or self.other_companies_power or self.nonstandard_equipment_power or self.multiple_electrical_environments:
return self.LARGE[0]
elif self.big_power:
return self.MEDIUM[0]
else:
return self.SMALL[0]
@property
def activity_feed_string(self):
return str(self.event)
def get_absolute_url(self):
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
def __str__(self):
return "%i - %s" % (self.pk, self.event)
@reversion.register(follow=['vehicles', 'crew'])
class EventChecklist(models.Model, RevisionMixin):
event = models.ForeignKey('Event', related_name='checklists', on_delete=models.CASCADE)
# General
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='checklists',
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?")
venue = models.ForeignKey('Venue', on_delete=models.CASCADE)
date = models.DateField()
# Safety Checks
safe_parking = models.BooleanField(blank=True, null=True, help_text="Vehicles parked safely?<br><small>(does not obstruct venue access)</small>")
safe_packing = models.BooleanField(blank=True, null=True, help_text="Equipment packed away safely?<br><small>(including flightcases)</small>")
exits = models.BooleanField(blank=True, null=True, help_text="Emergency exits clear?")
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box")
extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers")
# Small Electrical Checks
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
supply_test = models.BooleanField(blank=True, null=True, help_text="Electrical supplies tested?<br><small>(using socket tester)</small>")
# Shared electrical checks
earthing = models.BooleanField(blank=True, null=True, help_text="Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>")
pat = models.BooleanField(blank=True, null=True, help_text="All equipment in PAT period?")
# Medium Electrical Checks
source_rcd = models.BooleanField(blank=True, null=True, help_text="Source RCD protected?<br><small>(if cable is more than 3m long) </small>")
labelling = models.BooleanField(blank=True, null=True, help_text="Appropriate and clear labelling on distribution and cabling?")
# First Distro
fd_voltage_l1 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L1-N", help_text="L1 - N")
fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N")
fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N")
fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>")
fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
# Worst case points
w1_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")
reviewed_at = models.DateTimeField(null=True)
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name="Reviewer", on_delete=models.CASCADE)
inverted_fields = []
class Meta:
ordering = ['event']
permissions = [
('review_eventchecklist', 'Can review Event Checklists')
]
@property
def activity_feed_string(self):
return str(self.event)
def get_absolute_url(self):
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
def __str__(self):
return "%i - %s" % (self.pk, self.event)
@reversion.register
class EventChecklistVehicle(models.Model, RevisionMixin):
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
vehicle = models.CharField(max_length=255)
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='vehicles', on_delete=models.CASCADE)
reversion_hide = True
def __str__(self):
return "{} driven by {}".format(self.vehicle, str(self.driver))
@reversion.register
class EventChecklistCrew(models.Model, RevisionMixin):
checklist = models.ForeignKey('EventChecklist', related_name='crew', blank=True, on_delete=models.CASCADE)
crewmember = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='crewed', on_delete=models.CASCADE)
role = models.CharField(max_length=255)
start = models.DateTimeField()
end = models.DateTimeField()
reversion_hide = True
def clean(self):
if self.start > self.end:
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
def __str__(self):
return "{} ({})".format(str(self.crewmember), self.role)

View File

@@ -1,40 +1,39 @@
from io import BytesIO
import urllib.request
import urllib.error
import urllib.parse
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.views import generic
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.template.loader import get_template
from django.conf import settings
from django.urls import reverse
from django.core import signing
from django.http import HttpResponse
from django.core.exceptions import SuspiciousOperation
from django.db.models import Q
from django.contrib import messages
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from z3c.rml import rml2pdf
from PyPDF2 import PdfFileMerger, PdfFileReader
import simplejson
import premailer
from RIGS import models, forms
from PyRIGS import decorators
import copy
import datetime
import re
import copy
import urllib.error
import urllib.parse
import urllib.request
from io import BytesIO
import premailer
import simplejson
from PyPDF2 import PdfFileMerger, PdfFileReader
from django.conf import settings
from django.contrib import messages
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core import signing
from django.core.exceptions import SuspiciousOperation
from django.core.mail import EmailMultiAlternatives
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import generic
from z3c.rml import rml2pdf
from PyRIGS import decorators
from RIGS import models, forms
__author__ = 'ghost'
class RigboardIndex(generic.TemplateView):
template_name = 'RIGS/rigboard.html'
template_name = 'rigboard.html'
def get_context_data(self, **kwargs):
# get super context
@@ -42,11 +41,12 @@ class RigboardIndex(generic.TemplateView):
# call out method to get current events
context['events'] = models.Event.objects.current_events()
context['page_title'] = "Rigboard"
return context
class WebCalendar(generic.TemplateView):
template_name = 'RIGS/calendar.html'
template_name = 'calendar.html'
def get_context_data(self, **kwargs):
context = super(WebCalendar, self).get_context_data(**kwargs)
@@ -56,6 +56,7 @@ class WebCalendar(generic.TemplateView):
class EventDetail(generic.DetailView):
template_name = 'event_detail.html'
model = models.Event
@@ -78,39 +79,22 @@ class EventOembed(generic.View):
class EventEmbed(EventDetail):
template_name = 'RIGS/event_embed.html'
class EventRA(generic.base.RedirectView):
permanent = False
def get_redirect_url(self, *args, **kwargs):
event = get_object_or_404(models.Event, pk=kwargs['pk'])
if event.risk_assessment_edit_url:
return event.risk_assessment_edit_url
params = {
'entry.708610078': f'N{event.pk:05}',
'entry.905899507': event.name,
'entry.139491562': event.venue.name if event.venue else '',
'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''),
'entry.902421165': event.mic.name if event.mic else ''
}
return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params)
template_name = 'event_embed.html'
class EventCreate(generic.CreateView):
model = models.Event
form_class = forms.EventForm
template_name = 'event_form.html'
def get_context_data(self, **kwargs):
context = super(EventCreate, self).get_context_data(**kwargs)
context['page_title'] = "New Event"
context['edit'] = True
context['currentVAT'] = models.VatRate.objects.current_rate()
form = context['form']
if re.search('"-\d+"', form['items_json'].value()):
if hasattr(form, 'items_json') and re.search(r'"-\d+"', form['items_json'].value()):
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
@@ -127,9 +111,11 @@ class EventCreate(generic.CreateView):
class EventUpdate(generic.UpdateView):
model = models.Event
form_class = forms.EventForm
template_name = 'event_form.html'
def get_context_data(self, **kwargs):
context = super(EventUpdate, self).get_context_data(**kwargs)
context['page_title'] = "Event {}".format(self.object.display_id)
context['edit'] = True
form = context['form']
@@ -143,13 +129,15 @@ class EventUpdate(generic.UpdateView):
return context
def render_to_response(self, context, **response_kwargs):
if not hasattr(context, 'duplicate'):
if hasattr(context, 'duplicate') and not context['duplicate']:
# If this event has already been emailed to a client, show a warning
if self.object.auth_request_at is not None:
messages.info(self.request, 'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.')
messages.info(self.request,
'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.')
if hasattr(self.object, 'authorised'):
messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.')
messages.warning(self.request,
'This event has already been authorised by the client, any changes to the price will require reauthorisation.')
return super(EventUpdate, self).render_to_response(context, **response_kwargs)
def get_success_url(self):
@@ -162,6 +150,7 @@ class EventDuplicate(EventUpdate):
new = copy.copy(old) # Make a copy of the object in memory
new.based_on = old # Make the new event based on the old event
new.purchase_order = None # Remove old PO
new.status = new.PROVISIONAL # Return status to provisional
# Clear checked in by if it's a dry hire
if new.dry_hire is True:
@@ -182,6 +171,7 @@ class EventDuplicate(EventUpdate):
def get_context_data(self, **kwargs):
context = super(EventDuplicate, self).get_context_data(**kwargs)
context['page_title'] = "Duplicate of Event {}".format(self.object.display_id)
context["duplicate"] = True
return context
@@ -189,7 +179,7 @@ class EventDuplicate(EventUpdate):
class EventPrint(generic.View):
def get(self, request, pk):
object = get_object_or_404(models.Event, pk=pk)
template = get_template('RIGS/event_print.xml')
template = get_template('event_print.xml')
merger = PdfFileMerger()
@@ -197,16 +187,16 @@ class EventPrint(generic.View):
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
'regular': 'static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'static/fonts/OPENSANS-BOLD.TTF',
}
},
'quote': True,
'current_user': request.user,
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
}
rml = template.render(context)
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
@@ -218,19 +208,26 @@ class EventPrint(generic.View):
merger.write(merged)
response = HttpResponse(content_type='application/pdf')
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name)
response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName)
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
response.write(merged.getvalue())
return response
class EventArchive(generic.ArchiveIndexView):
class EventArchive(generic.ListView):
template_name = "event_archive.html"
model = models.Event
date_field = "start_date"
paginate_by = 25
def get_context_data(self, **kwargs):
# get super context
context = super(EventArchive, self).get_context_data(**kwargs)
context['start'] = self.request.GET.get('start', None)
context['end'] = self.request.GET.get('end', datetime.date.today().strftime('%Y-%m-%d'))
context['statuses'] = models.Event.EVENT_STATUS_CHOICES
context['page_title'] = 'Event Archive'
return context
def get_queryset(self):
start = self.request.GET.get('start', None)
end = self.request.GET.get('end', datetime.date.today())
@@ -241,19 +238,39 @@ class EventArchive(generic.ArchiveIndexView):
"Muppet! Check the dates, it has been fixed for you.")
start, end = end, start # Stop the impending fail
filter = False
filter = Q()
if end != "":
filter = Q(start_date__lte=end)
filter &= Q(start_date__lte=end)
if start:
if filter:
filter = filter & Q(start_date__gte=start)
else:
filter = Q(start_date__gte=start)
filter &= Q(start_date__gte=start)
if filter:
qs = self.model.objects.filter(filter).order_by('-start_date')
else:
qs = self.model.objects.all().order_by('-start_date')
q = self.request.GET.get('q', "")
if q != "":
qfilter = Q(name__icontains=q) | Q(description__icontains=q) | Q(notes__icontains=q)
# try and parse an int
try:
val = int(q)
qfilter = qfilter | Q(pk=val)
except: # noqa not an integer
pass
try:
if q[0] == "N":
val = int(q[1:])
qfilter = Q(pk=val) # If string is N###### then do a simple PK filter
except: # noqa
pass
filter &= qfilter
status = self.request.GET.getlist('status', "")
if len(status) > 0:
filter &= Q(status__in=status)
qs = self.model.objects.filter(filter).order_by('-start_date')
# Preselect related for efficiency
qs.select_related('person', 'organisation', 'venue', 'mic')
@@ -265,8 +282,8 @@ class EventArchive(generic.ArchiveIndexView):
class EventAuthorise(generic.UpdateView):
template_name = 'RIGS/eventauthorisation_form.html'
success_template = 'RIGS/eventauthorisation_success.html'
template_name = 'eventauthorisation_form.html'
success_template = 'eventauthorisation_success.html'
def form_valid(self, form):
self.object = form.save()
@@ -274,7 +291,7 @@ class EventAuthorise(generic.UpdateView):
self.template_name = self.success_template
messages.add_message(self.request, messages.SUCCESS,
'Success! Your event has been authorised. ' +
'You will also receive email confirmation to %s.' % (self.object.email))
'You will also receive email confirmation to %s.' % self.object.email)
return self.render_to_response(self.get_context_data())
@property
@@ -290,8 +307,10 @@ class EventAuthorise(generic.UpdateView):
def get_context_data(self, **kwargs):
context = super(EventAuthorise, self).get_context_data(**kwargs)
context['event'] = self.event
context['tos_url'] = settings.TERMS_OF_HIRE_URL
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
if self.event.dry_hire:
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
return context
def get(self, request, *args, **kwargs):
@@ -329,7 +348,7 @@ class EventAuthorise(generic.UpdateView):
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
model = models.Event
form_class = forms.EventAuthorisationRequestForm
template_name = 'RIGS/eventauthorisation_request.html'
template_name = 'eventauthorisation_request.html'
@method_decorator(decorators.nottinghamtec_address_required)
def dispatch(self, *args, **kwargs):
@@ -354,7 +373,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
email = form.cleaned_data['email']
event = self.object
event.auth_request_by = self.request.user
event.auth_request_at = datetime.datetime.now()
event.auth_request_at = timezone.now()
event.auth_request_to = email
event.save()
@@ -374,12 +393,12 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
msg = EmailMultiAlternatives(
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
get_template("RIGS/eventauthorisation_client_request.txt").render(context),
get_template("eventauthorisation_client_request.txt").render(context),
to=[email],
reply_to=[self.request.user.email],
)
css = staticfiles_storage.path('css/email.css')
html = premailer.Premailer(get_template("RIGS/eventauthorisation_client_request.html").render(context),
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
external_styles=css).transform()
msg.attach_alternative(html, 'text/html')
@@ -389,7 +408,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
class EventAuthoriseRequestEmailPreview(generic.DetailView):
template_name = "RIGS/eventauthorisation_client_request.html"
template_name = "eventauthorisation_client_request.html"
model = models.Event
def render_to_response(self, context, **response_kwargs):
@@ -409,27 +428,3 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
})
context['to_name'] = self.request.GET.get('to_name', None)
return context
@method_decorator(csrf_exempt, name='dispatch')
class LogRiskAssessment(generic.View):
http_method_names = ["post"]
def post(self, request, **kwargs):
data = request.POST
shared_secret = data.get("secret")
edit_url = data.get("editUrl")
rig_number = data.get("rigNum")
if shared_secret is None or edit_url is None or rig_number is None:
return HttpResponse(status=422)
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
return HttpResponse(status=403)
rig_number = int(re.sub("[^0-9]", "", rig_number))
event = get_object_or_404(models.Event, pk=rig_number)
event.risk_assessment_edit_url = edit_url
event.save()
return HttpResponse(status=200)

View File

@@ -1,20 +1,21 @@
import datetime
import re
import urllib.request
import urllib.error
import urllib.parse
import urllib.request
from io import BytesIO
from django.db.models.signals import post_save
from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.cache import cache
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.db.models.signals import post_save
from django.template.loader import get_template
from django.urls import reverse
from django.utils import timezone
from registration.signals import user_activated
from premailer import Premailer
from registration.signals import user_activated
from reversion import revisions as reversion
from z3c.rml import rml2pdf
from RIGS import models
@@ -34,7 +35,7 @@ def send_eventauthorisation_success_email(instance):
'current_user': False,
}
template = get_template('RIGS/event_print.xml')
template = get_template('event_print.xml')
merger = PdfFileMerger()
rml = template.render(context)
@@ -63,17 +64,17 @@ def send_eventauthorisation_success_email(instance):
client_email = EmailMultiAlternatives(
subject,
get_template("RIGS/eventauthorisation_client_success.txt").render(context),
get_template("eventauthorisation_client_success.txt").render(context),
to=[instance.email],
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
)
css = staticfiles_storage.path('css/email.css')
html = Premailer(get_template("RIGS/eventauthorisation_client_success.html").render(context),
html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
external_styles=css).transform()
client_email.attach_alternative(html, 'text/html')
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name)
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', instance.event.name)
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
merged.getvalue(),
@@ -87,7 +88,7 @@ def send_eventauthorisation_success_email(instance):
mic_email = EmailMessage(
subject,
get_template("RIGS/eventauthorisation_mic_success.txt").render(context),
get_template("eventauthorisation_mic_success.txt").render(context),
to=[mic_email_address]
)
@@ -122,12 +123,12 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
email = EmailMultiAlternatives(
"%s new users awaiting approval on RIGS" % (context['number_of_users']),
get_template("RIGS/admin_awaiting_approval.txt").render(context),
get_template("admin_awaiting_approval.txt").render(context),
to=[admin.email],
reply_to=[user.email],
)
css = staticfiles_storage.path('css/email.css')
html = Premailer(get_template("RIGS/admin_awaiting_approval.html").render(context),
html = Premailer(get_template("admin_awaiting_approval.html").render(context),
external_styles=css).transform()
email.attach_alternative(html, 'text/html')
email.send()
@@ -138,3 +139,11 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
user_activated.connect(send_admin_awaiting_approval_email)
def update_cache(sender, instance, created, **kwargs):
cache.clear()
for model in reversion.get_registered_models():
post_save.connect(update_cache, sender=model)

View File

@@ -1,27 +0,0 @@
# Require any additional compass plugins here.
require 'bootstrap-sass'
# Set this to the root of your project when deployed:
http_path = "/static/"
css_dir = "css"
sass_dir = "scss"
images_dir = "img"
javascripts_dir = "js"
fonts_dir = "fonts"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
output_style = :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
# If you prefer the indented syntax, you might want to regenerate this
# project again passing --syntax sass, or you can uncomment this:
# preferred_syntax = :sass
# and then run:
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass

View File

@@ -1,27 +0,0 @@
/*!
* Ajax Bootstrap Select
*
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
*
* @version 1.3.1
* @author Adam Heim - https://github.com/truckingsim
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
* @copyright 2015 Adam Heim
* @license Released under the MIT license.
*
* Contributors:
* Mark Carver - https://github.com/markcarver
*
* Last build: 2015-01-06 8:43:11 PM EST
*/
.bootstrap-select .status {
background: #f0f0f0;
clear: both;
color: #999;
font-size: 11px;
font-style: italic;
font-weight: 500;
line-height: 1;
margin-bottom: -5px;
padding: 10px 20px;
}

View File

@@ -1,366 +0,0 @@
/*!
* Datetimepicker for Bootstrap 3
* ! version : 4.7.14
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
.bootstrap-datetimepicker-widget {
list-style: none;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
margin: 2px 0;
padding: 4px;
width: 19em;
}
@media (min-width: 768px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: 992px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: 1200px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
.bootstrap-datetimepicker-widget.dropdown-menu:before,
.bootstrap-datetimepicker-widget.dropdown-menu:after {
content: '';
display: inline-block;
position: absolute;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #cccccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
top: -7px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
top: -6px;
left: 8px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #cccccc;
border-top-color: rgba(0, 0, 0, 0.2);
bottom: -7px;
left: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
bottom: -6px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before {
left: auto;
right: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after {
left: auto;
right: 7px;
}
.bootstrap-datetimepicker-widget .list-unstyled {
margin: 0;
}
.bootstrap-datetimepicker-widget a[data-action] {
padding: 6px 0;
}
.bootstrap-datetimepicker-widget a[data-action]:active {
box-shadow: none;
}
.bootstrap-datetimepicker-widget .timepicker-hour,
.bootstrap-datetimepicker-widget .timepicker-minute,
.bootstrap-datetimepicker-widget .timepicker-second {
width: 54px;
font-weight: bold;
font-size: 1.2em;
margin: 0;
}
.bootstrap-datetimepicker-widget button[data-action] {
padding: 6px;
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle AM/PM";
}
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Clear the picker";
}
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Set the date to today";
}
.bootstrap-datetimepicker-widget .picker-switch {
text-align: center;
}
.bootstrap-datetimepicker-widget .picker-switch::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle Date and Time Screens";
}
.bootstrap-datetimepicker-widget .picker-switch td {
padding: 0;
margin: 0;
height: auto;
width: auto;
line-height: inherit;
}
.bootstrap-datetimepicker-widget .picker-switch td span {
line-height: 2.5;
height: 2.5em;
width: 100%;
}
.bootstrap-datetimepicker-widget table {
width: 100%;
margin: 0;
}
.bootstrap-datetimepicker-widget table td,
.bootstrap-datetimepicker-widget table th {
text-align: center;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table th {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table th.picker-switch {
width: 145px;
}
.bootstrap-datetimepicker-widget table th.disabled,
.bootstrap-datetimepicker-widget table th.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table th.prev::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Previous Month";
}
.bootstrap-datetimepicker-widget table th.next::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Next Month";
}
.bootstrap-datetimepicker-widget table thead tr:first-child th {
cursor: pointer;
}
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {
background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td {
height: 54px;
line-height: 54px;
width: 54px;
}
.bootstrap-datetimepicker-widget table td.cw {
font-size: .8em;
height: 20px;
line-height: 20px;
color: #777777;
}
.bootstrap-datetimepicker-widget table td.day {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table td.day:hover,
.bootstrap-datetimepicker-widget table td.hour:hover,
.bootstrap-datetimepicker-widget table td.minute:hover,
.bootstrap-datetimepicker-widget table td.second:hover {
background: #eeeeee;
cursor: pointer;
}
.bootstrap-datetimepicker-widget table td.old,
.bootstrap-datetimepicker-widget table td.new {
color: #777777;
}
.bootstrap-datetimepicker-widget table td.today {
position: relative;
}
.bootstrap-datetimepicker-widget table td.today:before {
content: '';
display: inline-block;
border: 0 0 7px 7px solid transparent;
border-bottom-color: #337ab7;
border-top-color: rgba(0, 0, 0, 0.2);
position: absolute;
bottom: 4px;
right: 4px;
}
.bootstrap-datetimepicker-widget table td.active,
.bootstrap-datetimepicker-widget table td.active:hover {
background-color: #337ab7;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td.active.today:before {
border-bottom-color: #fff;
}
.bootstrap-datetimepicker-widget table td.disabled,
.bootstrap-datetimepicker-widget table td.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table td span {
display: inline-block;
width: 54px;
height: 54px;
line-height: 54px;
margin: 2px 1.5px;
cursor: pointer;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table td span:hover {
background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td span.active {
background-color: #337ab7;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td span.old {
color: #777777;
}
.bootstrap-datetimepicker-widget table td span.disabled,
.bootstrap-datetimepicker-widget table td span.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
height: 27px;
line-height: 27px;
}
.input-group.date .input-group-addon {
cursor: pointer;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
body{margin:0px}.main-table{width:100%;border-collapse:collapse}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;width:100%;margin-bottom:28px}.client-header .logos{width:100%;max-width:640px}.client-header img{height:110px}.content-container{width:100%}.content-container .content{font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;width:100%;max-width:600px;padding:10px;text-align:left}.content-container .content .button-container{width:100%}.content-container .content .button-container .button{padding:6px 12px;background-color:#357ebf;border-radius:4px}.content-container .content .button-container .button a{color:#fff;text-decoration:none}

File diff suppressed because it is too large Load Diff

View File

@@ -1,202 +0,0 @@
/*!
* FullCalendar v2.3.1 Print Stylesheet
* Docs & License: http://fullcalendar.io/
* (c) 2015 Adam Shaw
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
.fc {
max-width: 100% !important;
}
/* Global Event Restyling
--------------------------------------------------------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
page-break-inside: avoid;
}
.fc-event .fc-resizer {
display: none;
}
/* Table & Day-Row Restyling
--------------------------------------------------------------------------------------------------*/
th,
td,
hr,
thead,
tbody,
.fc-row {
border-color: #ccc !important;
background: #fff !important;
}
/* kill the overlaid, absolutely-positioned common components */
.fc-bg,
.fc-bgevent-skeleton,
.fc-highlight-skeleton,
.fc-helper-skeleton {
display: none;
}
/* don't force a min-height on rows (for DayGrid) */
.fc tbody .fc-row {
height: auto !important; /* undo height that JS set in distributeHeight */
min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
}
.fc tbody .fc-row .fc-content-skeleton {
position: static; /* undo .fc-rigid */
padding-bottom: 0 !important; /* use a more border-friendly method for this... */
}
.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
}
.fc tbody .fc-row .fc-content-skeleton table {
/* provides a min-height for the row, but only effective for IE, which exaggerates this value,
making it look more like 3em. for other browers, it will already be this tall */
height: 1em;
}
/* Undo month-view event limiting. Display all events and hide the "more" links
--------------------------------------------------------------------------------------------------*/
.fc-more-cell,
.fc-more {
display: none !important;
}
.fc tr.fc-limited {
display: table-row !important;
}
.fc td.fc-limited {
display: table-cell !important;
}
.fc-popover {
display: none; /* never display the "more.." popover in print mode */
}
/* TimeGrid Restyling
--------------------------------------------------------------------------------------------------*/
/* undo the min-height 100% trick used to fill the container's height */
.fc-time-grid {
min-height: 0 !important;
}
/* don't display the side axis at all ("all-day" and time cells) */
.fc-agenda-view .fc-axis {
display: none;
}
/* don't display the horizontal lines */
.fc-slats,
.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
display: none !important; /* important overrides inline declaration */
}
/* let the container that holds the events be naturally positioned and create real height */
.fc-time-grid .fc-content-skeleton {
position: static;
}
/* in case there are no events, we still want some height */
.fc-time-grid .fc-content-skeleton table {
height: 4em;
}
/* kill the horizontal spacing made by the event container. event margins will be done below */
.fc-time-grid .fc-event-container {
margin: 0 !important;
}
/* TimeGrid *Event* Restyling
--------------------------------------------------------------------------------------------------*/
/* naturally position events, vertically stacking them */
.fc-time-grid .fc-event {
position: static !important;
margin: 3px 2px !important;
}
/* for events that continue to a future day, give the bottom border back */
.fc-time-grid .fc-event.fc-not-end {
border-bottom-width: 1px !important;
}
/* indicate the event continues via "..." text */
.fc-time-grid .fc-event.fc-not-end:after {
content: "...";
}
/* for events that are continuations from previous days, give the top border back */
.fc-time-grid .fc-event.fc-not-start {
border-top-width: 1px !important;
}
/* indicate the event is a continuation via "..." text */
.fc-time-grid .fc-event.fc-not-start:before {
content: "...";
}
/* time */
/* undo a previous declaration and let the time text span to a second line */
.fc-time-grid .fc-event .fc-time {
white-space: normal !important;
}
/* hide the the time that is normally displayed... */
.fc-time-grid .fc-event .fc-time span {
display: none;
}
/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
.fc-time-grid .fc-event .fc-time:after {
content: attr(data-full);
}
/* Vertical Scroller & Containers
--------------------------------------------------------------------------------------------------*/
/* kill the scrollbars and allow natural height */
.fc-scroller,
.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */
.fc-time-grid-container { /* */
overflow: visible !important;
height: auto !important;
}
/* kill the horizontal border/padding used to compensate for scrollbars */
.fc-row {
border: 0 !important;
margin: 0 !important;
}
/* Button Controls
--------------------------------------------------------------------------------------------------*/
.fc-button-group,
.fc button {
display: none; /* don't display any button-related controls */
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -1,162 +0,0 @@
/* ========================================================================
* Bootstrap: affix.js v3.3.7
* http://getbootstrap.com/javascript/#affix
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// AFFIX CLASS DEFINITION
// ======================
var Affix = function (element, options) {
this.options = $.extend({}, Affix.DEFAULTS, options)
this.$target = $(this.options.target)
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
this.$element = $(element)
this.affixed = null
this.unpin = null
this.pinnedOffset = null
this.checkPosition()
}
Affix.VERSION = '3.3.7'
Affix.RESET = 'affix affix-top affix-bottom'
Affix.DEFAULTS = {
offset: 0,
target: window
}
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
var targetHeight = this.$target.height()
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
if (this.affixed == 'bottom') {
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
}
var initializing = this.affixed == null
var colliderTop = initializing ? scrollTop : position.top
var colliderHeight = initializing ? targetHeight : height
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
return false
}
Affix.prototype.getPinnedOffset = function () {
if (this.pinnedOffset) return this.pinnedOffset
this.$element.removeClass(Affix.RESET).addClass('affix')
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
return (this.pinnedOffset = position.top - scrollTop)
}
Affix.prototype.checkPositionWithEventLoop = function () {
setTimeout($.proxy(this.checkPosition, this), 1)
}
Affix.prototype.checkPosition = function () {
if (!this.$element.is(':visible')) return
var height = this.$element.height()
var offset = this.options.offset
var offsetTop = offset.top
var offsetBottom = offset.bottom
var scrollHeight = Math.max($(document).height(), $(document.body).height())
if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
if (this.affixed != affix) {
if (this.unpin != null) this.$element.css('top', '')
var affixType = 'affix' + (affix ? '-' + affix : '')
var e = $.Event(affixType + '.bs.affix')
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
this.affixed = affix
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
this.$element
.removeClass(Affix.RESET)
.addClass(affixType)
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
}
if (affix == 'bottom') {
this.$element.offset({
top: scrollHeight - height - offsetBottom
})
}
}
// AFFIX PLUGIN DEFINITION
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.affix')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.affix
$.fn.affix = Plugin
$.fn.affix.Constructor = Affix
// AFFIX NO CONFLICT
// =================
$.fn.affix.noConflict = function () {
$.fn.affix = old
return this
}
// AFFIX DATA-API
// ==============
$(window).on('load', function () {
$('[data-spy="affix"]').each(function () {
var $spy = $(this)
var data = $spy.data()
data.offset = data.offset || {}
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
if (data.offsetTop != null) data.offset.top = data.offsetTop
Plugin.call($spy, data)
})
})
}(jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -1,94 +0,0 @@
/* ========================================================================
* Bootstrap: alert.js v3.3.7
* http://getbootstrap.com/javascript/#alerts
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// ALERT CLASS DEFINITION
// ======================
var dismiss = '[data-dismiss="alert"]'
var Alert = function (el) {
$(el).on('click', dismiss, this.close)
}
Alert.VERSION = '3.3.7'
Alert.TRANSITION_DURATION = 150
Alert.prototype.close = function (e) {
var $this = $(this)
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = $(selector === '#' ? [] : selector)
if (e) e.preventDefault()
if (!$parent.length) {
$parent = $this.closest('.alert')
}
$parent.trigger(e = $.Event('close.bs.alert'))
if (e.isDefaultPrevented()) return
$parent.removeClass('in')
function removeElement() {
// detach from parent, fire event then clean up data
$parent.detach().trigger('closed.bs.alert').remove()
}
$.support.transition && $parent.hasClass('fade') ?
$parent
.one('bsTransitionEnd', removeElement)
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
removeElement()
}
// ALERT PLUGIN DEFINITION
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.alert')
if (!data) $this.data('bs.alert', (data = new Alert(this)))
if (typeof option == 'string') data[option].call($this)
})
}
var old = $.fn.alert
$.fn.alert = Plugin
$.fn.alert.Constructor = Alert
// ALERT NO CONFLICT
// =================
$.fn.alert.noConflict = function () {
$.fn.alert = old
return this
}
// ALERT DATA-API
// ==============
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
}(jQuery);

View File

@@ -1,73 +0,0 @@
(function(){function Asteroids(){if(!window.ASTEROIDS)
window.ASTEROIDS={enemiesKilled:0};function Vector(x,y){if(typeof x=='Object'){this.x=x.x;this.y=x.y;}else{this.x=x;this.y=y;}};Vector.prototype={cp:function(){return new Vector(this.x,this.y);},mul:function(factor){this.x*=factor;this.y*=factor;return this;},mulNew:function(factor){return new Vector(this.x*factor,this.y*factor);},add:function(vec){this.x+=vec.x;this.y+=vec.y;return this;},addNew:function(vec){return new Vector(this.x+vec.x,this.y+vec.y);},sub:function(vec){this.x-=vec.x;this.y-=vec.y;return this;},subNew:function(vec){return new Vector(this.x-vec.x,this.y-vec.y);},rotate:function(angle){var x=this.x,y=this.y;this.x=x*Math.cos(angle)-Math.sin(angle)*y;this.y=x*Math.sin(angle)+Math.cos(angle)*y;return this;},rotateNew:function(angle){return this.cp().rotate(angle);},setAngle:function(angle){var l=this.len();this.x=Math.cos(angle)*l;this.y=Math.sin(angle)*l;return this;},setAngleNew:function(angle){return this.cp().setAngle(angle);},setLength:function(length){var l=this.len();if(l)this.mul(length/l);else this.x=this.y=length;return this;},setLengthNew:function(length){return this.cp().setLength(length);},normalize:function(){var l=this.len();this.x/=l;this.y/=l;return this;},normalizeNew:function(){return this.cp().normalize();},angle:function(){return Math.atan2(this.y,this.x);},collidesWith:function(rect){return this.x>rect.x&&this.y>rect.y&&this.x<rect.x+rect.width&&this.y<rect.y+rect.height;},len:function(){var l=Math.sqrt(this.x*this.x+this.y*this.y);if(l<0.005&&l>-0.005)return 0;return l;},is:function(test){return typeof test=='object'&&this.x==test.x&&this.y==test.y;},toString:function(){return'[Vector('+this.x+', '+this.y+') angle: '+this.angle()+', length: '+this.len()+']';}};function Line(p1,p2){this.p1=p1;this.p2=p2;};Line.prototype={shift:function(pos){this.p1.add(pos);this.p2.add(pos);},intersectsWithRect:function(rect){var LL=new Vector(rect.x,rect.y+rect.height);var UL=new Vector(rect.x,rect.y);var LR=new Vector(rect.x+rect.width,rect.y+rect.height);var UR=new Vector(rect.x+rect.width,rect.y);if(this.p1.x>LL.x&&this.p1.x<UR.x&&this.p1.y<LL.y&&this.p1.y>UR.y&&this.p2.x>LL.x&&this.p2.x<UR.x&&this.p2.y<LL.y&&this.p2.y>UR.y)return true;if(this.intersectsLine(new Line(UL,LL)))return true;if(this.intersectsLine(new Line(LL,LR)))return true;if(this.intersectsLine(new Line(UL,UR)))return true;if(this.intersectsLine(new Line(UR,LR)))return true;return false;},intersectsLine:function(line2){var v1=this.p1,v2=this.p2;var v3=line2.p1,v4=line2.p2;var denom=((v4.y-v3.y)*(v2.x-v1.x))-((v4.x-v3.x)*(v2.y-v1.y));var numerator=((v4.x-v3.x)*(v1.y-v3.y))-((v4.y-v3.y)*(v1.x-v3.x));var numerator2=((v2.x-v1.x)*(v1.y-v3.y))-((v2.y-v1.y)*(v1.x-v3.x));if(denom==0.0){return false;}
var ua=numerator/denom;var ub=numerator2/denom;return(ua>=0.0&&ua<=1.0&&ub>=0.0&&ub<=1.0);}};var that=this;var isIE=!!window.ActiveXObject;var isIEQuirks=isIE&&document.compatMode=="BackCompat";var w=document.documentElement.clientWidth,h=document.documentElement.clientHeight;if(isIEQuirks){w=document.body.clientWidth;h=document.body.clientHeight;}
var playerWidth=20,playerHeight=30;var playerVerts=[[-1*playerHeight/2,-1*playerWidth/2],[-1*playerHeight/2,playerWidth/2],[playerHeight/2,0]];var ignoredTypes=['HTML','HEAD','BODY','SCRIPT','TITLE','META','STYLE','LINK'];if(window.ActiveXObject)
ignoredTypes=['HTML','HEAD','BODY','SCRIPT','TITLE','META','STYLE','LINK','SHAPE','LINE','GROUP','IMAGE','STROKE','FILL','SKEW','PATH','TEXTPATH','INS'];var hiddenTypes=['BR','HR'];var FPS=50;var acc=300;var maxSpeed=600;var rotSpeed=360;var bulletSpeed=700;var particleSpeed=400;var timeBetweenFire=150;var timeBetweenBlink=250;var timeBetweenEnemyUpdate=isIE?10000:2000;var bulletRadius=2;var maxParticles=isIE?20:40;var maxBullets=isIE?10:20;this.flame={r:[],y:[]};this.toggleBlinkStyle=function(){if(this.updated.blink.isActive){removeClass(document.body,'ASTEROIDSBLINK');}else{addClass(document.body,'ASTEROIDSBLINK');}
this.updated.blink.isActive=!this.updated.blink.isActive;};addStylesheet(".ASTEROIDSBLINK .ASTEROIDSYEAHENEMY","outline: 2px dotted red;");this.pos=new Vector(100,100);this.lastPos=false;this.vel=new Vector(0,0);this.dir=new Vector(0,1);this.keysPressed={};this.firedAt=false;this.updated={enemies:false,flame:new Date().getTime(),blink:{time:0,isActive:false}};this.scrollPos=new Vector(0,0);this.bullets=[];this.enemies=[];this.dying=[];this.totalEnemies=0;this.particles=[];function updateEnemyIndex(){for(var i=0,enemy;enemy=that.enemies[i];i++)
removeClass(enemy,"ASTEROIDSYEAHENEMY");var all=document.body.getElementsByTagName('*');that.enemies=[];for(var i=0,el;el=all[i];i++){if(indexOf(ignoredTypes,el.tagName.toUpperCase())==-1&&el.prefix!='g_vml_'&&hasOnlyTextualChildren(el)&&el.className!="ASTEROIDSYEAH"&&el.offsetHeight>0){el.aSize=size(el);that.enemies.push(el);addClass(el,"ASTEROIDSYEAHENEMY");if(!el.aAdded){el.aAdded=true;that.totalEnemies++;}}}};updateEnemyIndex();var createFlames;(function(){var rWidth=playerWidth,rIncrease=playerWidth*0.1,yWidth=playerWidth*0.6,yIncrease=yWidth*0.2,halfR=rWidth/2,halfY=yWidth/2,halfPlayerHeight=playerHeight/2;createFlames=function(){that.flame.r=[[-1*halfPlayerHeight,-1*halfR]];that.flame.y=[[-1*halfPlayerHeight,-1*halfY]];for(var x=0;x<rWidth;x+=rIncrease){that.flame.r.push([-random(2,7)-halfPlayerHeight,x-halfR]);}
that.flame.r.push([-1*halfPlayerHeight,halfR]);for(var x=0;x<yWidth;x+=yIncrease){that.flame.y.push([-random(2,7)-halfPlayerHeight,x-halfY]);}
that.flame.y.push([-1*halfPlayerHeight,halfY]);};})();createFlames();function radians(deg){return deg*0.0174532925;};function degrees(rad){return rad*57.2957795;};function random(from,to){return Math.floor(Math.random()*(to+1)+from);};function code(name){var table={'up':38,'down':40,'left':37,'right':39,'esc':27};if(table[name])return table[name];return name.charCodeAt(0);};function boundsCheck(vec){if(vec.x>w)
vec.x=0;else if(vec.x<0)
vec.x=w;if(vec.y>h)
vec.y=0;else if(vec.y<0)
vec.y=h;};function size(element){var el=element,left=0,top=0;do{left+=el.offsetLeft||0;top+=el.offsetTop||0;el=el.offsetParent;}while(el);return{x:left,y:top,width:element.offsetWidth||10,height:element.offsetHeight||10};};function addEvent(obj,type,fn){if(obj.addEventListener)
obj.addEventListener(type,fn,false);else if(obj.attachEvent){obj["e"+type+fn]=fn;obj[type+fn]=function(){obj["e"+type+fn](window.event);}
obj.attachEvent("on"+type,obj[type+fn]);}}
function removeEvent(obj,type,fn){if(obj.removeEventListener)
obj.removeEventListener(type,fn,false);else if(obj.detachEvent){obj.detachEvent("on"+type,obj[type+fn]);obj[type+fn]=null;obj["e"+type+fn]=null;}}
function arrayRemove(array,from,to){var rest=array.slice((to||from)+1||array.length);array.length=from<0?array.length+from:from;return array.push.apply(array,rest);};function applyVisibility(vis){for(var i=0,p;p=window.ASTEROIDSPLAYERS[i];i++){p.gameContainer.style.visibility=vis;}}
function getElementFromPoint(x,y){applyVisibility('hidden');var element=document.elementFromPoint(x,y);if(!element){applyVisibility('visible');return false;}
if(element.nodeType==3)
element=element.parentNode;applyVisibility('visible');return element;};function addParticles(startPos){var time=new Date().getTime();var amount=maxParticles;for(var i=0;i<amount;i++){that.particles.push({dir:(new Vector(Math.random()*20-10,Math.random()*20-10)).normalize(),pos:startPos.cp(),cameAlive:time});}};function setScore(){that.points.innerHTML=window.ASTEROIDS.enemiesKilled*10;};function hasOnlyTextualChildren(element){if(element.offsetLeft<-100&&element.offsetWidth>0&&element.offsetHeight>0)return false;if(indexOf(hiddenTypes,element.tagName)!=-1)return true;if(element.offsetWidth==0&&element.offsetHeight==0)return false;for(var i=0;i<element.childNodes.length;i++){if(indexOf(hiddenTypes,element.childNodes[i].tagName)==-1&&element.childNodes[i].childNodes.length!=0)return false;}
return true;};function indexOf(arr,item,from){if(arr.indexOf)return arr.indexOf(item,from);var len=arr.length;for(var i=(from<0)?Math.max(0,len+from):from||0;i<len;i++){if(arr[i]===item)return i;}
return-1;};function addClass(element,className){if(element.className.indexOf(className)==-1)
element.className=(element.className+' '+className).replace(/\s+/g,' ').replace(/^\s+|\s+$/g,'');};function removeClass(element,className){element.className=element.className.replace(new RegExp('(^|\\s)'+className+'(?:\\s|$)'),'$1');};function addStylesheet(selector,rules){var stylesheet=document.createElement('style');stylesheet.type='text/css';stylesheet.rel='stylesheet';stylesheet.id='ASTEROIDSYEAHSTYLES';try{stylesheet.innerHTML=selector+"{"+rules+"}";}catch(e){stylesheet.styleSheet.addRule(selector,rules);}
document.getElementsByTagName("head")[0].appendChild(stylesheet);};function removeStylesheet(name){var stylesheet=document.getElementById(name);if(stylesheet){stylesheet.parentNode.removeChild(stylesheet);}};this.gameContainer=document.createElement('div');this.gameContainer.className='ASTEROIDSYEAH';document.body.appendChild(this.gameContainer);this.canvas=document.createElement('canvas');this.canvas.setAttribute('width',w);this.canvas.setAttribute('height',h);this.canvas.className='ASTEROIDSYEAH';with(this.canvas.style){width=w+"px";height=h+"px";position="fixed";top="0px";left="0px";bottom="0px";right="0px";zIndex="10000";}
if(typeof G_vmlCanvasManager!='undefined'){this.canvas=G_vmlCanvasManager.initElement(this.canvas);if(!this.canvas.getContext){alert("So... you're using IE? Please join me at http://github.com/erkie/erkie.github.com if you think you can help");}}else{if(!this.canvas.getContext){alert('This program does not yet support your browser. Please join me at http://github.com/erkie/erkie.github.com if you think you can help');}}
addEvent(this.canvas,'mousedown',function(e){e=e||window.event;var message=document.createElement('span');message.style.position='absolute';message.style.border='1px solid #999';message.style.background='white';message.style.color="black";message.innerHTML='Press Esc to quit';document.body.appendChild(message);var x=e.pageX||(e.clientX+document.documentElement.scrollLeft);var y=e.pageY||(e.clientY+document.documentElement.scrollTop);message.style.left=x-message.offsetWidth/2+'px';message.style.top=y-message.offsetHeight/2+'px';setTimeout(function(){try{message.parentNode.removeChild(message);}catch(e){}},1000);});var eventResize=function(){if(!isIE){that.canvas.style.display="none";w=document.documentElement.clientWidth;h=document.documentElement.clientHeight;that.canvas.setAttribute('width',w);that.canvas.setAttribute('height',h);with(that.canvas.style){display="block";width=w+"px";height=h+"px";}}else{w=document.documentElement.clientWidth;h=document.documentElement.clientHeight;if(isIEQuirks){w=document.body.clientWidth;h=document.body.clientHeight;}
that.canvas.setAttribute('width',w);that.canvas.setAttribute('height',h);}};addEvent(window,'resize',eventResize);this.gameContainer.appendChild(this.canvas);this.ctx=this.canvas.getContext("2d");this.ctx.fillStyle="black";this.ctx.strokeStyle="black";if(!document.getElementById('ASTEROIDS-NAVIGATION')){this.navigation=document.createElement('div');this.navigation.id="ASTEROIDS-NAVIGATION";this.navigation.className="ASTEROIDSYEAH";with(this.navigation.style){fontFamily="Arial,sans-serif";position="fixed";zIndex="10001";bottom="10px";right="10px";textAlign="right";}
this.navigation.innerHTML="(press esc to quit) ";this.gameContainer.appendChild(this.navigation);this.points=document.createElement('span');this.points.id='ASTEROIDS-POINTS';this.points.style.font="28pt Arial, sans-serif";this.points.style.fontWeight="bold";this.points.className="ASTEROIDSYEAH";this.navigation.appendChild(this.points);}else{this.navigation=document.getElementById('ASTEROIDS-NAVIGATION');this.points=document.getElementById('ASTEROIDS-POINTS');}
if(isIEQuirks){this.gameContainer.style.position=this.canvas.style.position=this.navigation.style.position="absolute";}
setScore();if(typeof G_vmlCanvasManager!='undefined'){var children=this.canvas.getElementsByTagName('*');for(var i=0,c;c=children[i];i++)
addClass(c,'ASTEROIDSYEAH');}
var eventKeydown=function(event){event=event||window.event;that.keysPressed[event.keyCode]=true;switch(event.keyCode){case code(' '):that.firedAt=1;break;}
if(indexOf([code('up'),code('down'),code('right'),code('left'),code(' '),code('B'),code('W'),code('A'),code('S'),code('D')],event.keyCode)!=-1){if(event.preventDefault)
event.preventDefault();if(event.stopPropagation)
event.stopPropagation();event.returnValue=false;event.cancelBubble=true;return false;}};addEvent(document,'keydown',eventKeydown);var eventKeypress=function(event){event=event||window.event;if(indexOf([code('up'),code('down'),code('right'),code('left'),code(' '),code('W'),code('A'),code('S'),code('D')],event.keyCode||event.which)!=-1){if(event.preventDefault)
event.preventDefault();if(event.stopPropagation)
event.stopPropagation();event.returnValue=false;event.cancelBubble=true;return false;}};addEvent(document,'keypress',eventKeypress);var eventKeyup=function(event){event=event||window.event;that.keysPressed[event.keyCode]=false;if(indexOf([code('up'),code('down'),code('right'),code('left'),code(' '),code('B'),code('W'),code('A'),code('S'),code('D')],event.keyCode)!=-1){if(event.preventDefault)
event.preventDefault();if(event.stopPropagation)
event.stopPropagation();event.returnValue=false;event.cancelBubble=true;return false;}};addEvent(document,'keyup',eventKeyup);this.ctx.clear=function(){this.clearRect(0,0,w,h);};this.ctx.clear();this.ctx.drawLine=function(xFrom,yFrom,xTo,yTo){this.beginPath();this.moveTo(xFrom,yFrom);this.lineTo(xTo,yTo);this.lineTo(xTo+1,yTo+1);this.closePath();this.fill();};this.ctx.tracePoly=function(verts){this.beginPath();this.moveTo(verts[0][0],verts[0][1]);for(var i=1;i<verts.length;i++)
this.lineTo(verts[i][0],verts[i][1]);this.closePath();};this.ctx.drawPlayer=function(){this.save();this.translate(that.pos.x,that.pos.y);this.rotate(that.dir.angle());this.tracePoly(playerVerts);this.fillStyle="white";this.fill();this.tracePoly(playerVerts);this.stroke();this.restore();};var PI_SQ=Math.PI*2;this.ctx.drawBullets=function(bullets){for(var i=0;i<bullets.length;i++){this.beginPath();this.arc(bullets[i].pos.x,bullets[i].pos.y,bulletRadius,0,PI_SQ,true);this.closePath();this.fill();}};var randomParticleColor=function(){return(['red','yellow'])[random(0,1)];};this.ctx.drawParticles=function(particles){var oldColor=this.fillStyle;for(var i=0;i<particles.length;i++){this.fillStyle=randomParticleColor();this.drawLine(particles[i].pos.x,particles[i].pos.y,particles[i].pos.x-particles[i].dir.x*10,particles[i].pos.y-particles[i].dir.y*10);}
this.fillStyle=oldColor;};this.ctx.drawFlames=function(flame){this.save();this.translate(that.pos.x,that.pos.y);this.rotate(that.dir.angle());var oldColor=this.strokeStyle;this.strokeStyle="red";this.tracePoly(flame.r);this.stroke();this.strokeStyle="yellow";this.tracePoly(flame.y);this.stroke();this.strokeStyle=oldColor;this.restore();}
try{window.focus();}catch(e){}
addParticles(this.pos);addClass(document.body,'ASTEROIDSYEAH');var isRunning=true;var lastUpdate=new Date().getTime();this.update=function(){var forceChange=false;var nowTime=new Date().getTime();var tDelta=(nowTime-lastUpdate)/1000;lastUpdate=nowTime;var drawFlame=false;if(nowTime-this.updated.flame>50){createFlames();this.updated.flame=nowTime;}
this.scrollPos.x=window.pageXOffset||document.documentElement.scrollLeft;this.scrollPos.y=window.pageYOffset||document.documentElement.scrollTop;if((this.keysPressed[code('up')])||(this.keysPressed[code('W')])){this.vel.add(this.dir.mulNew(acc*tDelta));drawFlame=true;}else{this.vel.mul(0.96);}
if((this.keysPressed[code('left')])||(this.keysPressed[code('A')])){forceChange=true;this.dir.rotate(radians(rotSpeed*tDelta*-1));}
if((this.keysPressed[code('right')])||(this.keysPressed[code('D')])){forceChange=true;this.dir.rotate(radians(rotSpeed*tDelta));}
if(this.keysPressed[code(' ')]&&nowTime-this.firedAt>timeBetweenFire){this.bullets.unshift({'dir':this.dir.cp(),'pos':this.pos.cp(),'startVel':this.vel.cp(),'cameAlive':nowTime});this.firedAt=nowTime;if(this.bullets.length>maxBullets){this.bullets.pop();}}
if(this.keysPressed[code('B')]){if(!this.updated.enemies){updateEnemyIndex();this.updated.enemies=true;}
forceChange=true;this.updated.blink.time+=tDelta*1000;if(this.updated.blink.time>timeBetweenBlink){this.toggleBlinkStyle();this.updated.blink.time=0;}}else{this.updated.enemies=false;}
if(this.keysPressed[code('esc')]){destroy.apply(this);return;}
if(this.vel.len()>maxSpeed){this.vel.setLength(maxSpeed);}
this.pos.add(this.vel.mulNew(tDelta));if(this.pos.x>w){window.scrollTo(this.scrollPos.x+50,this.scrollPos.y);this.pos.x=0;}else if(this.pos.x<0){window.scrollTo(this.scrollPos.x-50,this.scrollPos.y);this.pos.x=w;}
if(this.pos.y>h){window.scrollTo(this.scrollPos.x,this.scrollPos.y+h*0.75);this.pos.y=0;}else if(this.pos.y<0){window.scrollTo(this.scrollPos.x,this.scrollPos.y-h*0.75);this.pos.y=h;}
for(var i=this.bullets.length-1;i>=0;i--){if(nowTime-this.bullets[i].cameAlive>2000){this.bullets.splice(i,1);forceChange=true;continue;}
var bulletVel=this.bullets[i].dir.setLengthNew(bulletSpeed*tDelta).add(this.bullets[i].startVel.mulNew(tDelta));this.bullets[i].pos.add(bulletVel);boundsCheck(this.bullets[i].pos);var murdered=getElementFromPoint(this.bullets[i].pos.x,this.bullets[i].pos.y);if(murdered&&murdered.tagName&&indexOf(ignoredTypes,murdered.tagName.toUpperCase())==-1&&hasOnlyTextualChildren(murdered)&&murdered.className!="ASTEROIDSYEAH"){didKill=true;addParticles(this.bullets[i].pos);this.dying.push(murdered);this.bullets.splice(i,1);continue;}}
if(this.dying.length){for(var i=this.dying.length-1;i>=0;i--){try{if(this.dying[i].parentNode)
window.ASTEROIDS.enemiesKilled++;this.dying[i].parentNode.removeChild(this.dying[i]);}catch(e){}}
setScore();this.dying=[];}
for(var i=this.particles.length-1;i>=0;i--){this.particles[i].pos.add(this.particles[i].dir.mulNew(particleSpeed*tDelta*Math.random()));if(nowTime-this.particles[i].cameAlive>1000){this.particles.splice(i,1);forceChange=true;continue;}}
if(isIEQuirks){this.gameContainer.style.left=this.canvas.style.left=document.documentElement.scrollLeft+"px";this.gameContainer.style.top=this.canvas.style.top=document.documentElement.scrollTop+"px";this.navigation.style.right="10px";this.navigation.style.top=document.documentElement.scrollTop+document.body.clientHeight-this.navigation.clientHeight-10+"px";}
if(forceChange||this.bullets.length!=0||this.particles.length!=0||!this.pos.is(this.lastPos)||this.vel.len()>0){this.ctx.clear();this.ctx.drawPlayer();if(drawFlame)
this.ctx.drawFlames(that.flame);if(this.bullets.length){this.ctx.drawBullets(this.bullets);}
if(this.particles.length){this.ctx.drawParticles(this.particles);}}
this.lastPos=this.pos;}
var updateFunc=function(){try{that.update.call(that);}
catch(e){clearInterval(interval);throw e;}};var interval=setInterval(updateFunc,1000/FPS);function destroy(){removeEvent(document,'keydown',eventKeydown);removeEvent(document,'keypress',eventKeypress);removeEvent(document,'keyup',eventKeyup);removeEvent(window,'resize',eventResize);isRunning=false;removeStylesheet("ASTEROIDSYEAHSTYLES");removeClass(document.body,'ASTEROIDSYEAH');this.gameContainer.parentNode.removeChild(this.gameContainer);};}
if(!window.ASTEROIDSPLAYERS)
window.ASTEROIDSPLAYERS=[];if(window.ActiveXObject&&!document.createElement('canvas').getContext){try{var xamlScript=document.createElement('script');xamlScript.setAttribute('type','text/xaml');xamlScript.textContent='<?xml version="1.0"?><Canvas xmlns="http://schemas.microsoft.com/client/2007"></Canvas>';document.getElementsByTagName('head')[0].appendChild(xamlScript);}catch(e){}
var script=document.createElement("script");script.setAttribute('type','text/javascript');script.onreadystatechange=function(){if(script.readyState=='loaded'||script.readyState=='complete'){if(typeof G_vmlCanvasManager!="undefined")
window.ASTEROIDSPLAYERS[window.ASTEROIDSPLAYERS.length]=new Asteroids();}};script.src="http://erkie.github.com/excanvas.js";document.getElementsByTagName('head')[0].appendChild(script);}
else window.ASTEROIDSPLAYERS[window.ASTEROIDSPLAYERS.length]=new Asteroids();})();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,125 +0,0 @@
/* ========================================================================
* Bootstrap: button.js v3.3.7
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// BUTTON PUBLIC CLASS DEFINITION
// ==============================
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Button.DEFAULTS, options)
this.isLoading = false
}
Button.VERSION = '3.3.7'
Button.DEFAULTS = {
loadingText: 'loading...'
}
Button.prototype.setState = function (state) {
var d = 'disabled'
var $el = this.$element
var val = $el.is('input') ? 'val' : 'html'
var data = $el.data()
state += 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]())
// push to event loop to allow forms to submit
setTimeout($.proxy(function () {
$el[val](data[state] == null ? this.options[state] : data[state])
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d).prop(d, true)
} else if (this.isLoading) {
this.isLoading = false
$el.removeClass(d).removeAttr(d).prop(d, false)
}
}, this), 0)
}
Button.prototype.toggle = function () {
var changed = true
var $parent = this.$element.closest('[data-toggle="buttons"]')
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked')) changed = false
$parent.find('.active').removeClass('active')
this.$element.addClass('active')
} else if ($input.prop('type') == 'checkbox') {
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
this.$element.toggleClass('active')
}
$input.prop('checked', this.$element.hasClass('active'))
if (changed) $input.trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
this.$element.toggleClass('active')
}
}
// BUTTON PLUGIN DEFINITION
// ========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.button')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
})
}
var old = $.fn.button
$.fn.button = Plugin
$.fn.button.Constructor = Button
// BUTTON NO CONFLICT
// ==================
$.fn.button.noConflict = function () {
$.fn.button = old
return this
}
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target).closest('.btn')
Plugin.call($btn, 'toggle')
if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
// Prevent double click on radios, and the double selections (so cancellation) on checkboxes
e.preventDefault()
// The target component still receive the focus
if ($btn.is('input,button')) $btn.trigger('focus')
else $btn.find('input:visible,button:visible').first().trigger('focus')
}
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
})
}(jQuery);

View File

@@ -1,237 +0,0 @@
/* ========================================================================
* Bootstrap: carousel.js v3.3.7
* http://getbootstrap.com/javascript/#carousel
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CAROUSEL CLASS DEFINITION
// =========================
var Carousel = function (element, options) {
this.$element = $(element)
this.$indicators = this.$element.find('.carousel-indicators')
this.options = options
this.paused = null
this.sliding = null
this.interval = null
this.$active = null
this.$items = null
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
.on('mouseenter.bs.carousel', $.proxy(this.pause, this))
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
}
Carousel.VERSION = '3.3.7'
Carousel.TRANSITION_DURATION = 600
Carousel.DEFAULTS = {
interval: 5000,
pause: 'hover',
wrap: true,
keyboard: true
}
Carousel.prototype.keydown = function (e) {
if (/input|textarea/i.test(e.target.tagName)) return
switch (e.which) {
case 37: this.prev(); break
case 39: this.next(); break
default: return
}
e.preventDefault()
}
Carousel.prototype.cycle = function (e) {
e || (this.paused = false)
this.interval && clearInterval(this.interval)
this.options.interval
&& !this.paused
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
return this
}
Carousel.prototype.getItemIndex = function (item) {
this.$items = item.parent().children('.item')
return this.$items.index(item || this.$active)
}
Carousel.prototype.getItemForDirection = function (direction, active) {
var activeIndex = this.getItemIndex(active)
var willWrap = (direction == 'prev' && activeIndex === 0)
|| (direction == 'next' && activeIndex == (this.$items.length - 1))
if (willWrap && !this.options.wrap) return active
var delta = direction == 'prev' ? -1 : 1
var itemIndex = (activeIndex + delta) % this.$items.length
return this.$items.eq(itemIndex)
}
Carousel.prototype.to = function (pos) {
var that = this
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
if (pos > (this.$items.length - 1) || pos < 0) return
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
if (activeIndex == pos) return this.pause().cycle()
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
}
Carousel.prototype.pause = function (e) {
e || (this.paused = true)
if (this.$element.find('.next, .prev').length && $.support.transition) {
this.$element.trigger($.support.transition.end)
this.cycle(true)
}
this.interval = clearInterval(this.interval)
return this
}
Carousel.prototype.next = function () {
if (this.sliding) return
return this.slide('next')
}
Carousel.prototype.prev = function () {
if (this.sliding) return
return this.slide('prev')
}
Carousel.prototype.slide = function (type, next) {
var $active = this.$element.find('.item.active')
var $next = next || this.getItemForDirection(type, $active)
var isCycling = this.interval
var direction = type == 'next' ? 'left' : 'right'
var that = this
if ($next.hasClass('active')) return (this.sliding = false)
var relatedTarget = $next[0]
var slideEvent = $.Event('slide.bs.carousel', {
relatedTarget: relatedTarget,
direction: direction
})
this.$element.trigger(slideEvent)
if (slideEvent.isDefaultPrevented()) return
this.sliding = true
isCycling && this.pause()
if (this.$indicators.length) {
this.$indicators.find('.active').removeClass('active')
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
$nextIndicator && $nextIndicator.addClass('active')
}
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
if ($.support.transition && this.$element.hasClass('slide')) {
$next.addClass(type)
$next[0].offsetWidth // force reflow
$active.addClass(direction)
$next.addClass(direction)
$active
.one('bsTransitionEnd', function () {
$next.removeClass([type, direction].join(' ')).addClass('active')
$active.removeClass(['active', direction].join(' '))
that.sliding = false
setTimeout(function () {
that.$element.trigger(slidEvent)
}, 0)
})
.emulateTransitionEnd(Carousel.TRANSITION_DURATION)
} else {
$active.removeClass('active')
$next.addClass('active')
this.sliding = false
this.$element.trigger(slidEvent)
}
isCycling && this.cycle()
return this
}
// CAROUSEL PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.carousel')
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
var action = typeof option == 'string' ? option : options.slide
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
if (typeof option == 'number') data.to(option)
else if (action) data[action]()
else if (options.interval) data.pause().cycle()
})
}
var old = $.fn.carousel
$.fn.carousel = Plugin
$.fn.carousel.Constructor = Carousel
// CAROUSEL NO CONFLICT
// ====================
$.fn.carousel.noConflict = function () {
$.fn.carousel = old
return this
}
// CAROUSEL DATA-API
// =================
var clickHandler = function (e) {
var href
var $this = $(this)
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
if (!$target.hasClass('carousel')) return
var options = $.extend({}, $target.data(), $this.data())
var slideIndex = $this.attr('data-slide-to')
if (slideIndex) options.interval = false
Plugin.call($target, options)
if (slideIndex) {
$target.data('bs.carousel').to(slideIndex)
}
e.preventDefault()
}
$(document)
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
$(window).on('load', function () {
$('[data-ride="carousel"]').each(function () {
var $carousel = $(this)
Plugin.call($carousel, $carousel.data())
})
})
}(jQuery);

View File

@@ -1,212 +0,0 @@
/* ========================================================================
* Bootstrap: collapse.js v3.3.7
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
/* jshint latedef: false */
+function ($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null
if (this.options.parent) {
this.$parent = this.getParent()
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
}
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.3.7'
Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = {
toggle: true
}
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return
var activesData
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
if (actives && actives.length) {
activesData = actives.data('bs.collapse')
if (activesData && activesData.transitioning) return
}
var startEvent = $.Event('show.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
if (actives && actives.length) {
Plugin.call(actives, 'hide')
activesData || actives.data('bs.collapse', null)
}
var dimension = this.dimension()
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true)
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true)
this.transitioning = 1
var complete = function () {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('')
this.transitioning = 0
this.$element
.trigger('shown.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
}
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return
var startEvent = $.Event('hide.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var dimension = this.dimension()
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false)
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false)
this.transitioning = 1
var complete = function () {
this.transitioning = 0
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
this.$element
[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
}
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
Collapse.prototype.getParent = function () {
return $(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function (i, element) {
var $element = $(element)
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
}, this))
.end()
}
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
$element.attr('aria-expanded', isOpen)
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
}
function getTargetFromTrigger($trigger) {
var href
var target = $trigger.attr('data-target')
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
return $(target)
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.collapse
$.fn.collapse = Plugin
$.fn.collapse.Constructor = Collapse
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old
return this
}
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this)
if (!$this.attr('data-target')) e.preventDefault()
var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
Plugin.call($target, option)
})
}(jQuery);

View File

@@ -1,165 +0,0 @@
/* ========================================================================
* Bootstrap: dropdown.js v3.3.7
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// DROPDOWN CLASS DEFINITION
// =========================
var backdrop = '.dropdown-backdrop'
var toggle = '[data-toggle="dropdown"]'
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle)
}
Dropdown.VERSION = '3.3.7'
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
})
}
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
clearMenus()
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$(document.createElement('div'))
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus)
}
var relatedTarget = { relatedTarget: this }
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this
.trigger('focus')
.attr('aria-expanded', 'true')
$parent
.toggleClass('open')
.trigger($.Event('shown.bs.dropdown', relatedTarget))
}
return false
}
Dropdown.prototype.keydown = function (e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
var $this = $(this)
e.preventDefault()
e.stopPropagation()
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
if (!isActive && e.which != 27 || isActive && e.which == 27) {
if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click')
}
var desc = ' li:not(.disabled):visible a'
var $items = $parent.find('.dropdown-menu' + desc)
if (!$items.length) return
var index = $items.index(e.target)
if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
$items.eq(index).trigger('focus')
}
// DROPDOWN PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.dropdown')
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
var old = $.fn.dropdown
$.fn.dropdown = Plugin
$.fn.dropdown.Constructor = Dropdown
// DROPDOWN NO CONFLICT
// ====================
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old
return this
}
// APPLY TO STANDARD DROPDOWN ELEMENTS
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
}(jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +0,0 @@
/*!
* jQuery Cookie Plugin v1.4.0
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define(['jquery'], factory);
} else {
// Browser globals.
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
} catch(e) {
return;
}
try {
// If we can't parse the cookie, ignore it, it's unusable.
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== undefined) {
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return true;
}
return false;
};
}));

View File

@@ -1,100 +0,0 @@
jQuery(function($) {
$
.widget(
"custom.combobox",
{
_create : function() {
this.wrapper = $("<span>").addClass(
"custom-combobox")
.insertAfter(this.element);
this.element.hide();
this._createAutocomplete();
},
_createAutocomplete : function() {
var selected = this.element.children(":selected"), value = selected
.val() ? selected.text() : "";
this.input = $("<input>").appendTo(this.wrapper)
.val(value).attr("title", "").addClass(
"form-control").autocomplete({
delay : 0,
minLength : 3,
source : $.proxy(this, "_source")
}).tooltip({
tooltipClass : "ui-state-highlight"
});
this._on(this.input, {
autocompleteselect : function(event, ui) {
ui.item.option.selected = true;
this._trigger("select", event, {
item : ui.item.option
});
},
autocompletechange : "_removeIfInvalid"
});
},
_source : function(request, response) {
var matcher = new RegExp($.ui.autocomplete
.escapeRegex(request.term), "i");
response(this.element.children("option").map(
function() {
var text = $(this).text();
if (this.value
&& (!request.term || matcher
.test(text)))
return {
label : text,
value : text,
option : this
};
}));
},
_removeIfInvalid : function(event, ui) {
// Selected an item, nothing to do
if (ui.item) {
return;
}
// Search for a match (case-insensitive)
var value = this.input.val(), valueLowerCase = value
.toLowerCase(), valid = false;
this.element
.children("option")
.each(
function() {
if ($(this).text()
.toLowerCase() === valueLowerCase) {
this.selected = valid = true;
return false;
}
});
// Found a match, nothing to do
if (valid) {
return;
}
// Remove invalid value
this.input.val("").attr("title",
value + " didn't match any item").tooltip(
"open");
this.element.val("");
this._delay(function() {
this.input.tooltip("close").attr("title", "");
}, 25000);
this.input.data("ui-autocomplete").term = "";
},
_destroy : function() {
this.wrapper.remove();
this.element.show();
}
});
});

View File

@@ -1,105 +0,0 @@
/*
* Konami-JS ~
* :: Now with support for touch events and multiple instances for
* :: those situations that call for multiple easter eggs!
* Code: http://konami-js.googlecode.com/
* Examples: http://www.snaptortoise.com/konami-js
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
* Version: 1.4.2 (9/2/2013)
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser
*/
var Konami = function (callback) {
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
}
obj.attachEvent("on" + type, obj[type + fn]);
}
},
input: "",
pattern: "38384040373937396665",
load: function (link) {
this.addEvent(document, "keydown", function (e, ref_obj) {
if (ref_obj) konami = ref_obj; // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length)
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
if (konami.input == konami.pattern) {
konami.code(link);
konami.input = "";
e.preventDefault();
return false;
}
}, this);
/*this.iphone.load(link);*/
},
code: function (link) {
window.location = link
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
code: function (link) {
konami.code(link);
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", function (e) {
if (e.touches.length == 1 && konami.iphone.capture == true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
});
konami.addEvent(document, "touchend", function (evt) {
if (konami.iphone.tap == true) konami.iphone.check_direction(link);
}, false);
konami.addEvent(document, "touchstart", function (evt) {
konami.iphone.start_x = evt.changedTouches[0].pageX;
konami.iphone.start_y = evt.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
});
},
check_direction: function (link) {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
result = (x_magnitude > y_magnitude) ? x : y;
result = (this.tap == true) ? "TAP" : result;
if (result == this.keys[0]) this.keys = this.keys.slice(1, this.keys.length);
if (this.keys.length == 0) {
this.keys = this.orig_keys;
this.code(link);
}
}
}
}
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
return konami;
};

View File

@@ -1,339 +0,0 @@
/* ========================================================================
* Bootstrap: modal.js v3.3.7
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// MODAL CLASS DEFINITION
// ======================
var Modal = function (element, options) {
this.options = options
this.$body = $(document.body)
this.$element = $(element)
this.$dialog = this.$element.find('.modal-dialog')
this.$backdrop = null
this.isShown = null
this.originalBodyPad = null
this.scrollbarWidth = 0
this.ignoreBackdropClick = false
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal')
}, this))
}
}
Modal.VERSION = '3.3.7'
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
Modal.DEFAULTS = {
backdrop: true,
keyboard: true,
show: true
}
Modal.prototype.toggle = function (_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget)
}
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
this.$element.trigger(e)
if (this.isShown || e.isDefaultPrevented()) return
this.isShown = true
this.checkScrollbar()
this.setScrollbar()
this.$body.addClass('modal-open')
this.escape()
this.resize()
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
})
})
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade')
if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // don't move modals dom position
}
that.$element
.show()
.scrollTop(0)
that.adjustDialog()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
that.enforceFocus()
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
transition ?
that.$dialog // wait for modal to slide in
.one('bsTransitionEnd', function () {
that.$element.trigger('focus').trigger(e)
})
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e)
})
}
Modal.prototype.hide = function (e) {
if (e) e.preventDefault()
e = $.Event('hide.bs.modal')
this.$element.trigger(e)
if (!this.isShown || e.isDefaultPrevented()) return
this.isShown = false
this.escape()
this.resize()
$(document).off('focusin.bs.modal')
this.$element
.removeClass('in')
.off('click.dismiss.bs.modal')
.off('mouseup.dismiss.bs.modal')
this.$dialog.off('mousedown.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal()
}
Modal.prototype.enforceFocus = function () {
$(document)
.off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) {
if (document !== e.target &&
this.$element[0] !== e.target &&
!this.$element.has(e.target).length) {
this.$element.trigger('focus')
}
}, this))
}
Modal.prototype.escape = function () {
if (this.isShown && this.options.keyboard) {
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
e.which == 27 && this.hide()
}, this))
} else if (!this.isShown) {
this.$element.off('keydown.dismiss.bs.modal')
}
}
Modal.prototype.resize = function () {
if (this.isShown) {
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
} else {
$(window).off('resize.bs.modal')
}
}
Modal.prototype.hideModal = function () {
var that = this
this.$element.hide()
this.backdrop(function () {
that.$body.removeClass('modal-open')
that.resetAdjustments()
that.resetScrollbar()
that.$element.trigger('hidden.bs.modal')
})
}
Modal.prototype.removeBackdrop = function () {
this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $(document.createElement('div'))
.addClass('modal-backdrop ' + animate)
.appendTo(this.$body)
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (this.ignoreBackdropClick) {
this.ignoreBackdropClick = false
return
}
if (e.target !== e.currentTarget) return
this.options.backdrop == 'static'
? this.$element[0].focus()
: this.hide()
}, this))
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
if (!callback) return
doAnimate ?
this.$backdrop
.one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
var callbackRemove = function () {
that.removeBackdrop()
callback && callback()
}
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove()
} else if (callback) {
callback()
}
}
// these following methods are used to handle overflowing modals
Modal.prototype.handleUpdate = function () {
this.adjustDialog()
}
Modal.prototype.adjustDialog = function () {
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
this.$element.css({
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
})
}
Modal.prototype.resetAdjustments = function () {
this.$element.css({
paddingLeft: '',
paddingRight: ''
})
}
Modal.prototype.checkScrollbar = function () {
var fullWindowWidth = window.innerWidth
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
var documentElementRect = document.documentElement.getBoundingClientRect()
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
}
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
this.scrollbarWidth = this.measureScrollbar()
}
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
this.originalBodyPad = document.body.style.paddingRight || ''
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', this.originalBodyPad)
}
Modal.prototype.measureScrollbar = function () { // thx walsh
var scrollDiv = document.createElement('div')
scrollDiv.className = 'modal-scrollbar-measure'
this.$body.append(scrollDiv)
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
this.$body[0].removeChild(scrollDiv)
return scrollbarWidth
}
// MODAL PLUGIN DEFINITION
// =======================
function Plugin(option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option](_relatedTarget)
else if (options.show) data.show(_relatedTarget)
})
}
var old = $.fn.modal
$.fn.modal = Plugin
$.fn.modal.Constructor = Modal
// MODAL NO CONFLICT
// =================
$.fn.modal.noConflict = function () {
$.fn.modal = old
return this
}
// MODAL DATA-API
// ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
if ($this.is('a')) e.preventDefault()
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus')
})
})
Plugin.call($target, option, this)
})
}(jQuery);

View File

@@ -1,86 +0,0 @@
(function() {
var day, formats, hour, initialize, minute, second, week;
second = 1e3;
minute = 6e4;
hour = 36e5;
day = 864e5;
week = 6048e5;
formats = {
seconds: {
short: 's',
long: ' sec'
},
minutes: {
short: 'm',
long: ' min'
},
hours: {
short: 'h',
long: ' hr'
},
days: {
short: 'd',
long: ' day'
}
};
initialize = function(moment) {
var twitterFormat;
twitterFormat = function(format) {
var diff, num, unit, unitStr;
diff = Math.abs(this.diff(moment()));
unit = null;
num = null;
if (diff <= second) {
unit = 'seconds';
num = 1;
} else if (diff < minute) {
unit = 'seconds';
} else if (diff < hour) {
unit = 'minutes';
} else if (diff < day) {
unit = 'hours';
} else if (format === 'short') {
if (diff < week) {
unit = 'days';
} else {
return this.format('M/D/YY');
}
} else {
return this.format('MMM D');
}
if (!(num && unit)) {
num = moment.duration(diff)[unit]();
}
unitStr = unit = formats[unit][format];
if (format === 'long' && num > 1) {
unitStr += 's';
}
return num + unitStr;
};
moment.fn.twitterLong = function() {
return twitterFormat.call(this, 'long');
};
moment.fn.twitter = moment.fn.twitterShort = function() {
return twitterFormat.call(this, 'short');
};
return moment;
};
if (typeof define === 'function' && define.amd) {
define('moment-twitter', ['moment'], function(moment) {
return this.moment = initialize(moment);
});
} else if (typeof module !== 'undefined') {
module.exports = initialize(require('moment'));
} else if (typeof window !== "undefined" && window.moment) {
this.moment = initialize(this.moment);
}
}).call(this);

File diff suppressed because one or more lines are too long

View File

@@ -1,108 +0,0 @@
/* ========================================================================
* Bootstrap: popover.js v3.3.7
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// POPOVER PUBLIC CLASS DEFINITION
// ===============================
var Popover = function (element, options) {
this.init('popover', element, options)
}
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
Popover.VERSION = '3.3.7'
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right',
trigger: 'click',
content: '',
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
})
// NOTE: POPOVER EXTENDS tooltip.js
// ================================
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
Popover.prototype.constructor = Popover
Popover.prototype.getDefaults = function () {
return Popover.DEFAULTS
}
Popover.prototype.setContent = function () {
var $tip = this.tip()
var title = this.getTitle()
var content = this.getContent()
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
](content)
$tip.removeClass('fade top bottom left right in')
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
}
Popover.prototype.hasContent = function () {
return this.getTitle() || this.getContent()
}
Popover.prototype.getContent = function () {
var $e = this.$element
var o = this.options
return $e.attr('data-content')
|| (typeof o.content == 'function' ?
o.content.call($e[0]) :
o.content)
}
Popover.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
}
// POPOVER PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.popover')
var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.popover
$.fn.popover = Plugin
$.fn.popover.Constructor = Popover
// POPOVER NO CONFLICT
// ===================
$.fn.popover.noConflict = function () {
$.fn.popover = old
return this
}
}(jQuery);

View File

@@ -1,172 +0,0 @@
/* ========================================================================
* Bootstrap: scrollspy.js v3.3.7
* http://getbootstrap.com/javascript/#scrollspy
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// SCROLLSPY CLASS DEFINITION
// ==========================
function ScrollSpy(element, options) {
this.$body = $(document.body)
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
this.selector = (this.options.target || '') + ' .nav li > a'
this.offsets = []
this.targets = []
this.activeTarget = null
this.scrollHeight = 0
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
this.refresh()
this.process()
}
ScrollSpy.VERSION = '3.3.7'
ScrollSpy.DEFAULTS = {
offset: 10
}
ScrollSpy.prototype.getScrollHeight = function () {
return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
}
ScrollSpy.prototype.refresh = function () {
var that = this
var offsetMethod = 'offset'
var offsetBase = 0
this.offsets = []
this.targets = []
this.scrollHeight = this.getScrollHeight()
if (!$.isWindow(this.$scrollElement[0])) {
offsetMethod = 'position'
offsetBase = this.$scrollElement.scrollTop()
}
this.$body
.find(this.selector)
.map(function () {
var $el = $(this)
var href = $el.data('target') || $el.attr('href')
var $href = /^#./.test(href) && $(href)
return ($href
&& $href.length
&& $href.is(':visible')
&& [[$href[offsetMethod]().top + offsetBase, href]]) || null
})
.sort(function (a, b) { return a[0] - b[0] })
.each(function () {
that.offsets.push(this[0])
that.targets.push(this[1])
})
}
ScrollSpy.prototype.process = function () {
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
var scrollHeight = this.getScrollHeight()
var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
var offsets = this.offsets
var targets = this.targets
var activeTarget = this.activeTarget
var i
if (this.scrollHeight != scrollHeight) {
this.refresh()
}
if (scrollTop >= maxScroll) {
return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
}
if (activeTarget && scrollTop < offsets[0]) {
this.activeTarget = null
return this.clear()
}
for (i = offsets.length; i--;) {
activeTarget != targets[i]
&& scrollTop >= offsets[i]
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
&& this.activate(targets[i])
}
}
ScrollSpy.prototype.activate = function (target) {
this.activeTarget = target
this.clear()
var selector = this.selector +
'[data-target="' + target + '"],' +
this.selector + '[href="' + target + '"]'
var active = $(selector)
.parents('li')
.addClass('active')
if (active.parent('.dropdown-menu').length) {
active = active
.closest('li.dropdown')
.addClass('active')
}
active.trigger('activate.bs.scrollspy')
}
ScrollSpy.prototype.clear = function () {
$(this.selector)
.parentsUntil(this.options.target, '.active')
.removeClass('active')
}
// SCROLLSPY PLUGIN DEFINITION
// ===========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.scrollspy')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.scrollspy
$.fn.scrollspy = Plugin
$.fn.scrollspy.Constructor = ScrollSpy
// SCROLLSPY NO CONFLICT
// =====================
$.fn.scrollspy.noConflict = function () {
$.fn.scrollspy = old
return this
}
// SCROLLSPY DATA-API
// ==================
$(window).on('load.bs.scrollspy.data-api', function () {
$('[data-spy="scroll"]').each(function () {
var $spy = $(this)
Plugin.call($spy, $spy.data())
})
})
}(jQuery);

View File

@@ -1,155 +0,0 @@
/* ========================================================================
* Bootstrap: tab.js v3.3.7
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TAB CLASS DEFINITION
// ====================
var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element)
// jscs:enable requireDollarBeforejQueryAssignment
}
Tab.VERSION = '3.3.7'
Tab.TRANSITION_DURATION = 150
Tab.prototype.show = function () {
var $this = this.element
var $ul = $this.closest('ul:not(.dropdown-menu)')
var selector = $this.data('target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
if ($this.parent('li').hasClass('active')) return
var $previous = $ul.find('.active:last a')
var hideEvent = $.Event('hide.bs.tab', {
relatedTarget: $this[0]
})
var showEvent = $.Event('show.bs.tab', {
relatedTarget: $previous[0]
})
$previous.trigger(hideEvent)
$this.trigger(showEvent)
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
var $target = $(selector)
this.activate($this.closest('li'), $ul)
this.activate($target, $target.parent(), function () {
$previous.trigger({
type: 'hidden.bs.tab',
relatedTarget: $this[0]
})
$this.trigger({
type: 'shown.bs.tab',
relatedTarget: $previous[0]
})
})
}
Tab.prototype.activate = function (element, container, callback) {
var $active = container.find('> .active')
var transition = callback
&& $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', false)
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if (element.parent('.dropdown-menu').length) {
element
.closest('li.dropdown')
.addClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
}
callback && callback()
}
$active.length && transition ?
$active
.one('bsTransitionEnd', next)
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
next()
$active.removeClass('in')
}
// TAB PLUGIN DEFINITION
// =====================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tab')
if (!data) $this.data('bs.tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tab
$.fn.tab = Plugin
$.fn.tab.Constructor = Tab
// TAB NO CONFLICT
// ===============
$.fn.tab.noConflict = function () {
$.fn.tab = old
return this
}
// TAB DATA-API
// ============
var clickHandler = function (e) {
e.preventDefault()
Plugin.call($(this), 'show')
}
$(document)
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
}(jQuery);

View File

@@ -1,52 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bootstrap Plugin Test Suite</title>
<!-- jquery -->
<!--<script src="http://code.jquery.com/jquery-1.7.min.js"></script>-->
<script src="vendor/jquery.js"></script>
<!-- qunit -->
<link rel="stylesheet" href="vendor/qunit.css" media="screen">
<script src="vendor/qunit.js"></script>
<!-- plugin sources -->
<script src="../transition.js"></script>
<script src="../alert.js"></script>
<script src="../button.js"></script>
<script src="../carousel.js"></script>
<script src="../collapse.js"></script>
<script src="../dropdown.js"></script>
<script src="../modal.js"></script>
<script src="../scrollspy.js"></script>
<script src="../tab.js"></script>
<script src="../tooltip.js"></script>
<script src="../popover.js"></script>
<script src="../affix.js"></script>
<!-- unit tests -->
<script src="unit/transition.js"></script>
<script src="unit/alert.js"></script>
<script src="unit/button.js"></script>
<script src="unit/carousel.js"></script>
<script src="unit/collapse.js"></script>
<script src="unit/dropdown.js"></script>
<script src="unit/modal.js"></script>
<script src="unit/scrollspy.js"></script>
<script src="unit/tab.js"></script>
<script src="unit/tooltip.js"></script>
<script src="unit/popover.js"></script>
<script src="unit/affix.js"></script>
</head>
<body>
<div>
<h1 id="qunit-header">Bootstrap Plugin Test Suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"></div>
</div>
</body>
</html>

View File

@@ -1,25 +0,0 @@
$(function () {
module("affix")
test("should provide no conflict", function () {
var affix = $.fn.affix.noConflict()
ok(!$.fn.affix, 'affix was set back to undefined (org value)')
$.fn.affix = affix
})
test("should be defined on jquery object", function () {
ok($(document.body).affix, 'affix method is defined')
})
test("should return element", function () {
ok($(document.body).affix()[0] == document.body, 'document.body returned')
})
test("should exit early if element is not visible", function () {
var $affix = $('<div style="display: none"></div>').affix()
$affix.data('bs.affix').checkPosition()
ok(!$affix.hasClass('affix'), 'affix class was not added')
})
})

View File

@@ -1,62 +0,0 @@
$(function () {
module("alert")
test("should provide no conflict", function () {
var alert = $.fn.alert.noConflict()
ok(!$.fn.alert, 'alert was set back to undefined (org value)')
$.fn.alert = alert
})
test("should be defined on jquery object", function () {
ok($(document.body).alert, 'alert method is defined')
})
test("should return element", function () {
ok($(document.body).alert()[0] == document.body, 'document.body returned')
})
test("should fade element out on clicking .close", function () {
var alertHTML = '<div class="alert-message warning fade in">'
+ '<a class="close" href="#" data-dismiss="alert">×</a>'
+ '<p><strong>Holy guacamole!</strong> Best check yo self, you\'re not looking too good.</p>'
+ '</div>'
, alert = $(alertHTML).alert()
alert.find('.close').click()
ok(!alert.hasClass('in'), 'remove .in class on .close click')
})
test("should remove element when clicking .close", function () {
$.support.transition = false
var alertHTML = '<div class="alert-message warning fade in">'
+ '<a class="close" href="#" data-dismiss="alert">×</a>'
+ '<p><strong>Holy guacamole!</strong> Best check yo self, you\'re not looking too good.</p>'
+ '</div>'
, alert = $(alertHTML).appendTo('#qunit-fixture').alert()
ok($('#qunit-fixture').find('.alert-message').length, 'element added to dom')
alert.find('.close').click()
ok(!$('#qunit-fixture').find('.alert-message').length, 'element removed from dom')
})
test("should not fire closed when close is prevented", function () {
$.support.transition = false
stop();
$('<div class="alert"/>')
.on('close.bs.alert', function (e) {
e.preventDefault();
ok(true);
start();
})
.on('closed.bs.alert', function () {
ok(false);
})
.alert('close')
})
})

View File

@@ -1,116 +0,0 @@
$(function () {
module("button")
test("should provide no conflict", function () {
var button = $.fn.button.noConflict()
ok(!$.fn.button, 'button was set back to undefined (org value)')
$.fn.button = button
})
test("should be defined on jquery object", function () {
ok($(document.body).button, 'button method is defined')
})
test("should return element", function () {
ok($(document.body).button()[0] == document.body, 'document.body returned')
})
test("should return set state to loading", function () {
var btn = $('<button class="btn" data-loading-text="fat">mdo</button>')
equal(btn.html(), 'mdo', 'btn text equals mdo')
btn.button('loading')
equal(btn.html(), 'fat', 'btn text equals fat')
stop()
setTimeout(function () {
ok(btn.attr('disabled'), 'btn is disabled')
ok(btn.hasClass('disabled'), 'btn has disabled class')
start()
}, 0)
})
test("should return reset state", function () {
var btn = $('<button class="btn" data-loading-text="fat">mdo</button>')
equal(btn.html(), 'mdo', 'btn text equals mdo')
btn.button('loading')
equal(btn.html(), 'fat', 'btn text equals fat')
stop()
setTimeout(function () {
ok(btn.attr('disabled'), 'btn is disabled')
ok(btn.hasClass('disabled'), 'btn has disabled class')
start()
stop()
btn.button('reset')
equal(btn.html(), 'mdo', 'btn text equals mdo')
setTimeout(function () {
ok(!btn.attr('disabled'), 'btn is not disabled')
ok(!btn.hasClass('disabled'), 'btn does not have disabled class')
start()
}, 0)
}, 0)
})
test("should toggle active", function () {
var btn = $('<button class="btn">mdo</button>')
ok(!btn.hasClass('active'), 'btn does not have active class')
btn.button('toggle')
ok(btn.hasClass('active'), 'btn has class active')
})
test("should toggle active when btn children are clicked", function () {
var btn = $('<button class="btn" data-toggle="button">mdo</button>')
, inner = $('<i></i>')
btn
.append(inner)
.appendTo($('#qunit-fixture'))
ok(!btn.hasClass('active'), 'btn does not have active class')
inner.click()
ok(btn.hasClass('active'), 'btn has class active')
})
test("should toggle active when btn children are clicked within btn-group", function () {
var btngroup = $('<div class="btn-group" data-toggle="buttons"></div>')
, btn = $('<button class="btn">fat</button>')
, inner = $('<i></i>')
btngroup
.append(btn.append(inner))
.appendTo($('#qunit-fixture'))
ok(!btn.hasClass('active'), 'btn does not have active class')
inner.click()
ok(btn.hasClass('active'), 'btn has class active')
})
test("should check for closest matching toggle", function () {
var group = '<div class="btn-group" data-toggle="buttons">' +
'<label class="btn btn-primary active">' +
'<input type="radio" name="options" id="option1" checked="true"> Option 1' +
'</label>' +
'<label class="btn btn-primary">' +
'<input type="radio" name="options" id="option2"> Option 2' +
'</label>' +
'<label class="btn btn-primary">' +
'<input type="radio" name="options" id="option3"> Option 3' +
'</label>' +
'</div>'
group = $(group)
var btn1 = $(group.children()[0])
var btn2 = $(group.children()[1])
var btn3 = $(group.children()[2])
group.appendTo($('#qunit-fixture'))
ok(btn1.hasClass('active'), 'btn1 has active class')
ok(btn1.find('input').prop('checked'), 'btn1 is checked')
ok(!btn2.hasClass('active'), 'btn2 does not have active class')
ok(!btn2.find('input').prop('checked'), 'btn2 is not checked')
btn2.find('input').click()
ok(!btn1.hasClass('active'), 'btn1 does not have active class')
ok(!btn1.find('input').prop('checked'), 'btn1 is checked')
ok(btn2.hasClass('active'), 'btn2 has active class')
ok(btn2.find('input').prop('checked'), 'btn2 is checked')
})
})

View File

@@ -1,87 +0,0 @@
$(function () {
module("carousel")
test("should provide no conflict", function () {
var carousel = $.fn.carousel.noConflict()
ok(!$.fn.carousel, 'carousel was set back to undefined (org value)')
$.fn.carousel = carousel
})
test("should be defined on jquery object", function () {
ok($(document.body).carousel, 'carousel method is defined')
})
test("should return element", function () {
ok($(document.body).carousel()[0] == document.body, 'document.body returned')
})
test("should not fire sliden when slide is prevented", function () {
$.support.transition = false
stop()
$('<div class="carousel"/>')
.on('slide.bs.carousel', function (e) {
e.preventDefault();
ok(true);
start();
})
.on('slid.bs.carousel', function () {
ok(false);
})
.carousel('next')
})
test("should fire slide event with direction", function () {
var template = '<div id="myCarousel" class="carousel slide"><div class="carousel-inner"><div class="item active"><img alt=""><div class="carousel-caption"><h4>{{_i}}First Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Second Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Third Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div></div><a class="left carousel-control" href="#myCarousel" data-slide="prev">&lsaquo;</a><a class="right carousel-control" href="#myCarousel" data-slide="next">&rsaquo;</a></div>'
$.support.transition = false
stop()
$(template).on('slide.bs.carousel', function (e) {
e.preventDefault()
ok(e.direction)
ok(e.direction === 'right' || e.direction === 'left')
start()
}).carousel('next')
})
test("should fire slide event with relatedTarget", function () {
var template = '<div id="myCarousel" class="carousel slide"><div class="carousel-inner"><div class="item active"><img alt=""><div class="carousel-caption"><h4>{{_i}}First Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Second Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Third Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div></div><a class="left carousel-control" href="#myCarousel" data-slide="prev">&lsaquo;</a><a class="right carousel-control" href="#myCarousel" data-slide="next">&rsaquo;</a></div>'
$.support.transition = false
stop()
$(template)
.on('slide.bs.carousel', function (e) {
e.preventDefault();
ok(e.relatedTarget);
ok($(e.relatedTarget).hasClass('item'));
start();
})
.carousel('next')
})
test("should set interval from data attribute", 4, function () {
var template = $('<div id="myCarousel" class="carousel slide"> <div class="carousel-inner"> <div class="item active"> <img alt=""> <div class="carousel-caption"> <h4>{{_i}}First Thumbnail label{{/i}}</h4> <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> </div> </div> <div class="item"> <img alt=""> <div class="carousel-caption"> <h4>{{_i}}Second Thumbnail label{{/i}}</h4> <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> </div> </div> <div class="item"> <img alt=""> <div class="carousel-caption"> <h4>{{_i}}Third Thumbnail label{{/i}}</h4> <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> </div> </div> </div> <a class="left carousel-control" href="#myCarousel" data-slide="prev">&lsaquo;</a> <a class="right carousel-control" href="#myCarousel" data-slide="next">&rsaquo;</a> </div>');
template.attr("data-interval", 1814);
template.appendTo("body");
$('[data-slide]').first().click();
ok($('#myCarousel').data('bs.carousel').options.interval == 1814);
$('#myCarousel').remove();
template.appendTo("body").attr("data-modal", "foobar");
$('[data-slide]').first().click();
ok($('#myCarousel').data('bs.carousel').options.interval == 1814, "even if there is an data-modal attribute set");
$('#myCarousel').remove();
template.appendTo("body");
$('[data-slide]').first().click();
$('#myCarousel').attr('data-interval', 1860);
$('[data-slide]').first().click();
ok($('#myCarousel').data('bs.carousel').options.interval == 1814, "attributes should be read only on intitialization");
$('#myCarousel').remove();
template.attr("data-interval", false);
template.appendTo("body");
$('#myCarousel').carousel(1);
ok($('#myCarousel').data('bs.carousel').options.interval === false, "data attribute has higher priority than default options");
$('#myCarousel').remove();
})
})

View File

@@ -1,164 +0,0 @@
$(function () {
module("collapse")
test("should provide no conflict", function () {
var collapse = $.fn.collapse.noConflict()
ok(!$.fn.collapse, 'collapse was set back to undefined (org value)')
$.fn.collapse = collapse
})
test("should be defined on jquery object", function () {
ok($(document.body).collapse, 'collapse method is defined')
})
test("should return element", function () {
ok($(document.body).collapse()[0] == document.body, 'document.body returned')
})
test("should show a collapsed element", function () {
var el = $('<div class="collapse"></div>').collapse('show')
ok(el.hasClass('in'), 'has class in')
ok(/height/.test(el.attr('style')), 'has height set')
})
test("should hide a collapsed element", function () {
var el = $('<div class="collapse"></div>').collapse('hide')
ok(!el.hasClass('in'), 'does not have class in')
ok(/height/.test(el.attr('style')), 'has height set')
})
test("should not fire shown when show is prevented", function () {
$.support.transition = false
stop()
$('<div class="collapse"/>')
.on('show.bs.collapse', function (e) {
e.preventDefault();
ok(true);
start();
})
.on('shown.bs.collapse', function () {
ok(false);
})
.collapse('show')
})
test("should reset style to auto after finishing opening collapse", function () {
$.support.transition = false
stop()
$('<div class="collapse" style="height: 0px"/>')
.on('show.bs.collapse', function () {
ok(this.style.height == '0px')
})
.on('shown.bs.collapse', function () {
ok(this.style.height == 'auto')
start()
})
.collapse('show')
})
test("should add active class to target when collapse shown", function () {
$.support.transition = false
stop()
var target = $('<a data-toggle="collapse" href="#test1"></a>')
.appendTo($('#qunit-fixture'))
var collapsible = $('<div id="test1"></div>')
.appendTo($('#qunit-fixture'))
.on('show.bs.collapse', function () {
ok(!target.hasClass('collapsed'))
start()
})
target.click()
})
test("should remove active class to target when collapse hidden", function () {
$.support.transition = false
stop()
var target = $('<a data-toggle="collapse" href="#test1"></a>')
.appendTo($('#qunit-fixture'))
var collapsible = $('<div id="test1" class="in"></div>')
.appendTo($('#qunit-fixture'))
.on('hide.bs.collapse', function () {
ok(target.hasClass('collapsed'))
start()
})
target.click()
})
test("should remove active class from inactive accordion targets", function () {
$.support.transition = false
stop()
var accordion = $('<div id="accordion"><div class="accordion-group"></div><div class="accordion-group"></div><div class="accordion-group"></div></div>')
.appendTo($('#qunit-fixture'))
var target1 = $('<a data-toggle="collapse" href="#body1" data-parent="#accordion"></a>')
.appendTo(accordion.find('.accordion-group').eq(0))
var collapsible1 = $('<div id="body1" class="in"></div>')
.appendTo(accordion.find('.accordion-group').eq(0))
var target2 = $('<a class="collapsed" data-toggle="collapse" href="#body2" data-parent="#accordion"></a>')
.appendTo(accordion.find('.accordion-group').eq(1))
var collapsible2 = $('<div id="body2"></div>')
.appendTo(accordion.find('.accordion-group').eq(1))
var target3 = $('<a class="collapsed" data-toggle="collapse" href="#body3" data-parent="#accordion"></a>')
.appendTo(accordion.find('.accordion-group').eq(2))
var collapsible3 = $('<div id="body3"></div>')
.appendTo(accordion.find('.accordion-group').eq(2))
.on('show.bs.collapse', function () {
ok(target1.hasClass('collapsed'))
ok(target2.hasClass('collapsed'))
ok(!target3.hasClass('collapsed'))
start()
})
target3.click()
})
test("should allow dots in data-parent", function () {
$.support.transition = false
stop()
var accordion = $('<div class="accordion"><div class="accordion-group"></div><div class="accordion-group"></div><div class="accordion-group"></div></div>')
.appendTo($('#qunit-fixture'))
var target1 = $('<a data-toggle="collapse" href="#body1" data-parent=".accordion"></a>')
.appendTo(accordion.find('.accordion-group').eq(0))
var collapsible1 = $('<div id="body1" class="in"></div>')
.appendTo(accordion.find('.accordion-group').eq(0))
var target2 = $('<a class="collapsed" data-toggle="collapse" href="#body2" data-parent=".accordion"></a>')
.appendTo(accordion.find('.accordion-group').eq(1))
var collapsible2 = $('<div id="body2"></div>')
.appendTo(accordion.find('.accordion-group').eq(1))
var target3 = $('<a class="collapsed" data-toggle="collapse" href="#body3" data-parent=".accordion"></a>')
.appendTo(accordion.find('.accordion-group').eq(2))
var collapsible3 = $('<div id="body3"></div>')
.appendTo(accordion.find('.accordion-group').eq(2))
.on('show.bs.collapse', function () {
ok(target1.hasClass('collapsed'))
ok(target2.hasClass('collapsed'))
ok(!target3.hasClass('collapsed'))
start()
})
target3.click()
})
})

View File

@@ -1,219 +0,0 @@
$(function () {
module("dropdowns")
test("should provide no conflict", function () {
var dropdown = $.fn.dropdown.noConflict()
ok(!$.fn.dropdown, 'dropdown was set back to undefined (org value)')
$.fn.dropdown = dropdown
})
test("should be defined on jquery object", function () {
ok($(document.body).dropdown, 'dropdown method is defined')
})
test("should return element", function () {
var el = $("<div />")
ok(el.dropdown()[0] === el[0], 'same element returned')
})
test("should not open dropdown if target is disabled", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
ok(!dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
})
test("should not open dropdown if target is disabled", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
ok(!dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
})
test("should add class open to menu if clicked", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
ok(dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
})
test("should test if element has a # before assuming it's a selector", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<a href="/foo/" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
ok(dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
})
test("should remove open class if body clicked", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.dropdown()
.click()
ok(dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
$('body').click()
ok(!dropdown.parent('.dropdown').hasClass('open'), 'open class removed')
dropdown.remove()
})
test("should remove open class if body clicked, with multiple drop downs", function () {
var dropdownHTML =
'<ul class="nav">'
+ ' <li><a href="#menu1">Menu 1</a></li>'
+ ' <li class="dropdown" id="testmenu">'
+ ' <a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <b class="caret"></b></a>'
+ ' <ul class="dropdown-menu" role="menu">'
+ ' <li><a href="#sub1">Submenu 1</a></li>'
+ ' </ul>'
+ ' </li>'
+ '</ul>'
+ '<div class="btn-group">'
+ ' <button class="btn">Actions</button>'
+ ' <button class="btn dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>'
+ ' <ul class="dropdown-menu">'
+ ' <li><a href="#">Action 1</a></li>'
+ ' </ul>'
+ '</div>'
, dropdowns = $(dropdownHTML).appendTo('#qunit-fixture').find('[data-toggle="dropdown"]')
, first = dropdowns.first()
, last = dropdowns.last()
ok(dropdowns.length == 2, "Should be two dropdowns")
first.click()
ok(first.parents('.open').length == 1, 'open class added on click')
ok($('#qunit-fixture .open').length == 1, 'only one object is open')
$('body').click()
ok($("#qunit-fixture .open").length === 0, 'open class removed')
last.click()
ok(last.parent('.open').length == 1, 'open class added on click')
ok($('#qunit-fixture .open').length == 1, 'only one object is open')
$('body').click()
ok($("#qunit-fixture .open").length === 0, 'open class removed')
$("#qunit-fixture").html("")
})
test("should fire show and hide event", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.dropdown()
stop()
dropdown
.parent('.dropdown')
.bind('show.bs.dropdown', function () {
ok(true, 'show was called')
})
.bind('hide.bs.dropdown', function () {
ok(true, 'hide was called')
start()
})
dropdown.click()
$(document.body).click()
})
test("should fire shown and hiden event", function () {
var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>'
+ '<li><a href="#">Something else here</a></li>'
+ '<li class="divider"></li>'
+ '<li><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
, dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.dropdown()
stop()
dropdown
.parent('.dropdown')
.bind('shown.bs.dropdown', function () {
ok(true, 'show was called')
})
.bind('hidden.bs.dropdown', function () {
ok(true, 'hide was called')
start()
})
dropdown.click()
$(document.body).click()
})
})

View File

@@ -1,196 +0,0 @@
$(function () {
module("modal")
test("should provide no conflict", function () {
var modal = $.fn.modal.noConflict()
ok(!$.fn.modal, 'modal was set back to undefined (org value)')
$.fn.modal = modal
})
test("should be defined on jquery object", function () {
var div = $("<div id='modal-test'></div>")
ok(div.modal, 'modal method is defined')
})
test("should return element", function () {
var div = $("<div id='modal-test'></div>")
ok(div.modal() == div, 'document.body returned')
$('#modal-test').remove()
})
test("should expose defaults var for settings", function () {
ok($.fn.modal.Constructor.DEFAULTS, 'default object exposed')
})
test("should insert into dom when show method is called", function () {
stop()
$.support.transition = false
$("<div id='modal-test'></div>")
.on("shown.bs.modal", function () {
ok($('#modal-test').length, 'modal inserted into dom')
$(this).remove()
start()
})
.modal("show")
})
test("should fire show event", function () {
stop()
$.support.transition = false
$("<div id='modal-test'></div>")
.on("show.bs.modal", function () {
ok(true, "show was called")
})
.on("shown.bs.modal", function () {
$(this).remove()
start()
})
.modal("show")
})
test("should not fire shown when default prevented", function () {
stop()
$.support.transition = false
$("<div id='modal-test'></div>")
.on("show.bs.modal", function (e) {
e.preventDefault()
ok(true, "show was called")
start()
})
.on("shown.bs.modal", function () {
ok(false, "shown was called")
})
.modal("show")
})
test("should hide modal when hide is called", function () {
stop()
$.support.transition = false
$("<div id='modal-test'></div>")
.on("shown.bs.modal", function () {
ok($('#modal-test').is(":visible"), 'modal visible')
ok($('#modal-test').length, 'modal inserted into dom')
$(this).modal("hide")
})
.on("hidden.bs.modal", function() {
ok(!$('#modal-test').is(":visible"), 'modal hidden')
$('#modal-test').remove()
start()
})
.modal("show")
})
test("should toggle when toggle is called", function () {
stop()
$.support.transition = false
var div = $("<div id='modal-test'></div>")
div
.on("shown.bs.modal", function () {
ok($('#modal-test').is(":visible"), 'modal visible')
ok($('#modal-test').length, 'modal inserted into dom')
div.modal("toggle")
})
.on("hidden.bs.modal", function() {
ok(!$('#modal-test').is(":visible"), 'modal hidden')
div.remove()
start()
})
.modal("toggle")
})
test("should remove from dom when click [data-dismiss=modal]", function () {
stop()
$.support.transition = false
var div = $("<div id='modal-test'><span class='close' data-dismiss='modal'></span></div>")
div
.on("shown.bs.modal", function () {
ok($('#modal-test').is(":visible"), 'modal visible')
ok($('#modal-test').length, 'modal inserted into dom')
div.find('.close').click()
})
.on("hidden.bs.modal", function() {
ok(!$('#modal-test').is(":visible"), 'modal hidden')
div.remove()
start()
})
.modal("toggle")
})
test("should allow modal close with 'backdrop:false'", function () {
stop()
$.support.transition = false
var div = $("<div>", { id: 'modal-test', "data-backdrop": false })
div
.on("shown.bs.modal", function () {
ok($('#modal-test').is(":visible"), 'modal visible')
div.modal("hide")
})
.on("hidden.bs.modal", function() {
ok(!$('#modal-test').is(":visible"), 'modal hidden')
div.remove()
start()
})
.modal("show")
})
test("should close modal when clicking outside of modal-content", function () {
stop()
$.support.transition = false
var div = $("<div id='modal-test'><div class='contents'></div></div>")
div
.bind("shown.bs.modal", function () {
ok($('#modal-test').length, 'modal insterted into dom')
$('.contents').click()
ok($('#modal-test').is(":visible"), 'modal visible')
$('#modal-test').click()
})
.bind("hidden.bs.modal", function() {
ok(!$('#modal-test').is(":visible"), 'modal hidden')
div.remove()
start()
})
.modal("show")
})
test("should trigger hide event once when clicking outside of modal-content", function () {
stop()
$.support.transition = false
var div = $("<div id='modal-test'><div class='contents'></div></div>")
var triggered
div
.bind("shown.bs.modal", function () {
triggered = 0
$('#modal-test').click()
})
.one("hidden.bs.modal", function() {
div.modal("show")
})
.bind("hide.bs.modal", function () {
triggered += 1
ok(triggered === 1, 'modal hide triggered once')
start()
})
.modal("show")
})
test("should close reopened modal with [data-dismiss=modal] click", function () {
stop()
$.support.transition = false
var div = $("<div id='modal-test'><div class='contents'><div id='close' data-dismiss='modal'></div></div></div>")
div
.bind("shown.bs.modal", function () {
$('#close').click()
ok(!$('#modal-test').is(":visible"), 'modal hidden')
})
.one("hidden.bs.modal", function() {
div.one('hidden.bs.modal', function () {
start()
}).modal("show")
})
.modal("show")
div.remove()
})
})

View File

@@ -1,69 +0,0 @@
/*
* grunt-contrib-qunit
* http://gruntjs.com/
*
* Copyright (c) 2013 "Cowboy" Ben Alman, contributors
* Licensed under the MIT license.
*/
/*global QUnit:true, alert:true*/
(function () {
'use strict';
// Don't re-order tests.
QUnit.config.reorder = false
// Run tests serially, not in parallel.
QUnit.config.autorun = false
// Send messages to the parent PhantomJS process via alert! Good times!!
function sendMessage() {
var args = [].slice.call(arguments)
alert(JSON.stringify(args))
}
// These methods connect QUnit to PhantomJS.
QUnit.log = function(obj) {
// What is this I dont even
if (obj.message === '[object Object], undefined:undefined') { return }
// Parse some stuff before sending it.
var actual = QUnit.jsDump.parse(obj.actual)
var expected = QUnit.jsDump.parse(obj.expected)
// Send it.
sendMessage('qunit.log', obj.result, actual, expected, obj.message, obj.source)
}
QUnit.testStart = function(obj) {
sendMessage('qunit.testStart', obj.name)
}
QUnit.testDone = function(obj) {
sendMessage('qunit.testDone', obj.name, obj.failed, obj.passed, obj.total)
}
QUnit.moduleStart = function(obj) {
sendMessage('qunit.moduleStart', obj.name)
}
QUnit.begin = function () {
sendMessage('qunit.begin')
console.log("Starting test suite")
console.log("================================================\n")
}
QUnit.moduleDone = function (opts) {
if (opts.failed === 0) {
console.log("\r\u2714 All tests passed in '" + opts.name + "' module")
} else {
console.log("\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module")
}
sendMessage('qunit.moduleDone', opts.name, opts.failed, opts.passed, opts.total)
}
QUnit.done = function (opts) {
console.log("\n================================================")
console.log("Tests completed in " + opts.runtime + " milliseconds")
console.log(opts.passed + " tests of " + opts.total + " passed, " + opts.failed + " failed.")
sendMessage('qunit.done', opts.failed, opts.passed, opts.total, opts.runtime)
}
}())

View File

@@ -1,133 +0,0 @@
$(function () {
module("popover")
test("should provide no conflict", function () {
var popover = $.fn.popover.noConflict()
ok(!$.fn.popover, 'popover was set back to undefined (org value)')
$.fn.popover = popover
})
test("should be defined on jquery object", function () {
var div = $('<div></div>')
ok(div.popover, 'popover method is defined')
})
test("should return element", function () {
var div = $('<div></div>')
ok(div.popover() == div, 'document.body returned')
})
test("should render popover element", function () {
$.support.transition = false
var popover = $('<a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>')
.appendTo('#qunit-fixture')
.popover('show')
ok($('.popover').length, 'popover was inserted')
popover.popover('hide')
ok(!$(".popover").length, 'popover removed')
})
test("should store popover instance in popover data object", function () {
$.support.transition = false
var popover = $('<a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>')
.popover()
ok(!!popover.data('bs.popover'), 'popover instance exists')
})
test("should get title and content from options", function () {
$.support.transition = false
var popover = $('<a href="#">@fat</a>')
.appendTo('#qunit-fixture')
.popover({
title: function () {
return '@fat'
}
, content: function () {
return 'loves writing tests (╯°□°)╯︵ ┻━┻'
}
})
popover.popover('show')
ok($('.popover').length, 'popover was inserted')
equal($('.popover .popover-title').text(), '@fat', 'title correctly inserted')
equal($('.popover .popover-content').text(), 'loves writing tests (╯°□°)╯︵ ┻━┻', 'content correctly inserted')
popover.popover('hide')
ok(!$('.popover').length, 'popover was removed')
$('#qunit-fixture').empty()
})
test("should get title and content from attributes", function () {
$.support.transition = false
var popover = $('<a href="#" title="@mdo" data-content="loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻" >@mdo</a>')
.appendTo('#qunit-fixture')
.popover()
.popover('show')
ok($('.popover').length, 'popover was inserted')
equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
equal($('.popover .popover-content').text(), "loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻", 'content correctly inserted')
popover.popover('hide')
ok(!$('.popover').length, 'popover was removed')
$('#qunit-fixture').empty()
})
test("should get title and content from attributes #2", function () {
$.support.transition = false
var popover = $('<a href="#" title="@mdo" data-content="loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻" >@mdo</a>')
.appendTo('#qunit-fixture')
.popover({
title: 'ignored title option',
content: 'ignored content option'
})
.popover('show')
ok($('.popover').length, 'popover was inserted')
equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
equal($('.popover .popover-content').text(), "loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻", 'content correctly inserted')
popover.popover('hide')
ok(!$('.popover').length, 'popover was removed')
$('#qunit-fixture').empty()
})
test("should respect custom classes", function() {
$.support.transition = false
var popover = $('<a href="#">@fat</a>')
.appendTo('#qunit-fixture')
.popover({
title: 'Test'
, content: 'Test'
, template: '<div class="popover foobar"><div class="arrow"></div><div class="inner"><h3 class="title"></h3><div class="content"><p></p></div></div></div>'
})
popover.popover('show')
ok($('.popover').length, 'popover was inserted')
ok($('.popover').hasClass('foobar'), 'custom class is present')
popover.popover('hide')
ok(!$('.popover').length, 'popover was removed')
$('#qunit-fixture').empty()
})
test("should destroy popover", function () {
var popover = $('<div/>').popover({trigger: 'hover'}).on('click.foo', function(){})
ok(popover.data('bs.popover'), 'popover has data')
ok($._data(popover[0], 'events').mouseover && $._data(popover[0], 'events').mouseout, 'popover has hover event')
ok($._data(popover[0], 'events').click[0].namespace == 'foo', 'popover has extra click.foo event')
popover.popover('show')
popover.popover('destroy')
ok(!popover.hasClass('in'), 'popover is hidden')
ok(!popover.data('popover'), 'popover does not have data')
ok($._data(popover[0],'events').click[0].namespace == 'foo', 'popover still has click.foo')
ok(!$._data(popover[0], 'events').mouseover && !$._data(popover[0], 'events').mouseout, 'popover does not have any events')
})
})

View File

@@ -1,37 +0,0 @@
$(function () {
module("scrollspy")
test("should provide no conflict", function () {
var scrollspy = $.fn.scrollspy.noConflict()
ok(!$.fn.scrollspy, 'scrollspy was set back to undefined (org value)')
$.fn.scrollspy = scrollspy
})
test("should be defined on jquery object", function () {
ok($(document.body).scrollspy, 'scrollspy method is defined')
})
test("should return element", function () {
ok($(document.body).scrollspy()[0] == document.body, 'document.body returned')
})
test("should switch active class on scroll", function () {
var sectionHTML = '<div id="masthead"></div>'
, $section = $(sectionHTML).append('#qunit-fixture')
, topbarHTML ='<div class="topbar">'
+ '<div class="topbar-inner">'
+ '<div class="container">'
+ '<h3><a href="#">Bootstrap</a></h3>'
+ '<ul class="nav">'
+ '<li><a href="#masthead">Overview</a></li>'
+ '</ul>'
+ '</div>'
+ '</div>'
+ '</div>'
, $topbar = $(topbarHTML).scrollspy()
ok($topbar.find('.active', true))
})
})

View File

@@ -1,86 +0,0 @@
$(function () {
module("tabs")
test("should provide no conflict", function () {
var tab = $.fn.tab.noConflict()
ok(!$.fn.tab, 'tab was set back to undefined (org value)')
$.fn.tab = tab
})
test("should be defined on jquery object", function () {
ok($(document.body).tab, 'tabs method is defined')
})
test("should return element", function () {
ok($(document.body).tab()[0] == document.body, 'document.body returned')
})
test("should activate element by tab id", function () {
var tabsHTML =
'<ul class="tabs">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
$('<ul><li id="home"></li><li id="profile"></li></ul>').appendTo("#qunit-fixture")
$(tabsHTML).find('li:last a').tab('show')
equal($("#qunit-fixture").find('.active').attr('id'), "profile")
$(tabsHTML).find('li:first a').tab('show')
equal($("#qunit-fixture").find('.active').attr('id'), "home")
})
test("should activate element by tab id", function () {
var pillsHTML =
'<ul class="pills">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
$('<ul><li id="home"></li><li id="profile"></li></ul>').appendTo("#qunit-fixture")
$(pillsHTML).find('li:last a').tab('show')
equal($("#qunit-fixture").find('.active').attr('id'), "profile")
$(pillsHTML).find('li:first a').tab('show')
equal($("#qunit-fixture").find('.active').attr('id'), "home")
})
test("should not fire closed when close is prevented", function () {
$.support.transition = false
stop();
$('<div class="tab"/>')
.on('show.bs.tab', function (e) {
e.preventDefault();
ok(true);
start();
})
.on('shown.bs.tab', function () {
ok(false);
})
.tab('show')
})
test("show and shown events should reference correct relatedTarget", function () {
var dropHTML =
'<ul class="drop">'
+ '<li class="dropdown"><a data-toggle="dropdown" href="#">1</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#1-1" data-toggle="tab">1-1</a></li>'
+ '<li><a href="#1-2" data-toggle="tab">1-2</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
$(dropHTML).find('ul>li:first a').tab('show').end()
.find('ul>li:last a').on('show', function(event){
equal(event.relatedTarget.hash, "#1-1")
}).on('shown', function(event){
equal(event.relatedTarget.hash, "#1-1")
}).tab('show')
})
})

View File

@@ -1,437 +0,0 @@
$(function () {
module("tooltip")
test("should provide no conflict", function () {
var tooltip = $.fn.tooltip.noConflict()
ok(!$.fn.tooltip, 'tooltip was set back to undefined (org value)')
$.fn.tooltip = tooltip
})
test("should be defined on jquery object", function () {
var div = $("<div></div>")
ok(div.tooltip, 'popover method is defined')
})
test("should return element", function () {
var div = $("<div></div>")
ok(div.tooltip() == div, 'document.body returned')
})
test("should expose default settings", function () {
ok(!!$.fn.tooltip.Constructor.DEFAULTS, 'defaults is defined')
})
test("should empty title attribute", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>').tooltip()
ok(tooltip.attr('title') === '', 'title attribute was emptied')
})
test("should add data attribute for referencing original title", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>').tooltip()
equal(tooltip.attr('data-original-title'), 'Another tooltip', 'original title preserved in data attribute')
})
test("should place tooltips relative to placement option", function () {
$.support.transition = false
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({placement: 'bottom'})
.tooltip('show')
ok($(".tooltip").is('.fade.bottom.in'), 'has correct classes applied')
tooltip.tooltip('hide')
})
test("should allow html entities", function () {
$.support.transition = false
var tooltip = $('<a href="#" rel="tooltip" title="<b>@fat</b>"></a>')
.appendTo('#qunit-fixture')
.tooltip({html: true})
.tooltip('show')
ok($('.tooltip b').length, 'b tag was inserted')
tooltip.tooltip('hide')
ok(!$(".tooltip").length, 'tooltip removed')
})
test("should respect custom classes", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ template: '<div class="tooltip some-class"><div class="tooltip-arrow"/><div class="tooltip-inner"/></div>'})
.tooltip('show')
ok($('.tooltip').hasClass('some-class'), 'custom class is present')
tooltip.tooltip('hide')
ok(!$(".tooltip").length, 'tooltip removed')
})
test("should fire show event", function () {
stop()
var tooltip = $('<div title="tooltip title"></div>')
.on("show.bs.tooltip", function() {
ok(true, "show was called")
start()
})
.tooltip('show')
})
test("should fire shown event", function () {
stop()
var tooltip = $('<div title="tooltip title"></div>')
.on("shown.bs.tooltip", function() {
ok(true, "shown was called")
start()
})
.tooltip('show')
})
test("should not fire shown event when default prevented", function () {
stop()
var tooltip = $('<div title="tooltip title"></div>')
.on("show.bs.tooltip", function(e) {
e.preventDefault()
ok(true, "show was called")
start()
})
.on("shown.bs.tooltip", function() {
ok(false, "shown was called")
})
.tooltip('show')
})
test("should fire hide event", function () {
stop()
var tooltip = $('<div title="tooltip title"></div>')
.on("shown.bs.tooltip", function() {
$(this).tooltip('hide')
})
.on("hide.bs.tooltip", function() {
ok(true, "hide was called")
start()
})
.tooltip('show')
})
test("should fire hidden event", function () {
stop()
var tooltip = $('<div title="tooltip title"></div>')
.on("shown.bs.tooltip", function() {
$(this).tooltip('hide')
})
.on("hidden.bs.tooltip", function() {
ok(true, "hidden was called")
start()
})
.tooltip('show')
})
test("should not fire hidden event when default prevented", function () {
stop()
var tooltip = $('<div title="tooltip title"></div>')
.on("shown.bs.tooltip", function() {
$(this).tooltip('hide')
})
.on("hide.bs.tooltip", function(e) {
e.preventDefault()
ok(true, "hide was called")
start()
})
.on("hidden.bs.tooltip", function() {
ok(false, "hidden was called")
})
.tooltip('show')
})
test("should not show tooltip if leave event occurs before delay expires", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ delay: 200 })
stop()
tooltip.trigger('mouseenter')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
tooltip.trigger('mouseout')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
start()
}, 200)
}, 100)
})
test("should not show tooltip if leave event occurs before delay expires, even if hide delay is 0", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ delay: { show: 200, hide: 0} })
stop()
tooltip.trigger('mouseenter')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
tooltip.trigger('mouseout')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
start()
}, 200)
}, 100)
})
test("should wait 200 ms before hiding the tooltip", 3, function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ delay: { show: 0, hide: 200} })
stop()
tooltip.trigger('mouseenter')
setTimeout(function () {
ok($(".tooltip").is('.fade.in'), 'tooltip is faded in')
tooltip.trigger('mouseout')
setTimeout(function () {
ok($(".tooltip").is('.fade.in'), '100ms:tooltip is still faded in')
setTimeout(function () {
ok(!$(".tooltip").is('.in'), 'tooltip removed')
start()
}, 150)
}, 100)
}, 1)
})
test("should not hide tooltip if leave event occurs, then tooltip is show immediately again", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ delay: { show: 0, hide: 200} })
stop()
tooltip.trigger('mouseenter')
setTimeout(function () {
ok($(".tooltip").is('.fade.in'), 'tooltip is faded in')
tooltip.trigger('mouseout')
setTimeout(function () {
ok($(".tooltip").is('.fade.in'), '100ms:tooltip is still faded in')
tooltip.trigger('mouseenter')
setTimeout(function () {
ok($(".tooltip").is('.in'), 'tooltip removed')
start()
}, 150)
}, 100)
}, 1)
})
test("should not show tooltip if leave event occurs before delay expires", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ delay: 100 })
stop()
tooltip.trigger('mouseenter')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
tooltip.trigger('mouseout')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
start()
}, 100)
}, 50)
})
test("should show tooltip if leave event hasn't occured before delay expires", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({ delay: 150 })
stop()
tooltip.trigger('mouseenter')
setTimeout(function () {
ok(!$(".tooltip").is('.fade.in'), 'tooltip is not faded in')
}, 100)
setTimeout(function () {
ok($(".tooltip").is('.fade.in'), 'tooltip has faded in')
start()
}, 200)
})
test("should destroy tooltip", function () {
var tooltip = $('<div/>').tooltip().on('click.foo', function(){})
ok(tooltip.data('bs.tooltip'), 'tooltip has data')
ok($._data(tooltip[0], 'events').mouseover && $._data(tooltip[0], 'events').mouseout, 'tooltip has hover event')
ok($._data(tooltip[0], 'events').click[0].namespace == 'foo', 'tooltip has extra click.foo event')
tooltip.tooltip('show')
tooltip.tooltip('destroy')
ok(!tooltip.hasClass('in'), 'tooltip is hidden')
ok(!$._data(tooltip[0], 'bs.tooltip'), 'tooltip does not have data')
ok($._data(tooltip[0], 'events').click[0].namespace == 'foo', 'tooltip still has click.foo')
ok(!$._data(tooltip[0], 'events').mouseover && !$._data(tooltip[0], 'events').mouseout, 'tooltip does not have any events')
})
test("should show tooltip with delegate selector on click", function () {
var div = $('<div><a href="#" rel="tooltip" title="Another tooltip"></a></div>')
var tooltip = div.appendTo('#qunit-fixture')
.tooltip({ selector: 'a[rel=tooltip]',
trigger: 'click' })
div.find('a').trigger('click')
ok($(".tooltip").is('.fade.in'), 'tooltip is faded in')
})
test("should show tooltip when toggle is called", function () {
var tooltip = $('<a href="#" rel="tooltip" title="tooltip on toggle"></a>')
.appendTo('#qunit-fixture')
.tooltip({trigger: 'manual'})
.tooltip('toggle')
ok($(".tooltip").is('.fade.in'), 'tooltip should be toggled in')
})
test("should place tooltips inside the body", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.tooltip({container:'body'})
.tooltip('show')
ok($("body > .tooltip").length, 'inside the body')
ok(!$("#qunit-fixture > .tooltip").length, 'not found in parent')
tooltip.tooltip('hide')
})
test("should place tooltip inside window", function(){
var container = $("<div />").appendTo("body")
.css({position: "absolute", width: 200, height: 200, bottom: 0, left: 0})
, tooltip = $("<a href='#' title='Very very very very very very very very long tooltip'>Hover me</a>")
.css({position: "absolute", top:0, left: 0})
.appendTo(container)
.tooltip({placement: "top", animate: false})
.tooltip("show")
stop()
setTimeout(function(){
ok($(".tooltip").offset().left >= 0)
start()
container.remove()
}, 100)
})
test("should place tooltip on top of element", function(){
var container = $("<div />").appendTo("body")
.css({position: "absolute", bottom: 0, left: 0, textAlign: "right", width: 300, height: 300})
, p = $("<p style='margin-top:200px' />").appendTo(container)
, tooltiped = $("<a href='#' title='very very very very very very very long tooltip'>Hover me</a>")
.css({marginTop: 200})
.appendTo(p)
.tooltip({placement: "top", animate: false})
.tooltip("show")
stop()
setTimeout(function(){
var tooltip = container.find(".tooltip")
start()
ok(tooltip.offset().top + tooltip.outerHeight() <= tooltiped.offset().top)
container.remove()
}, 100)
})
test("should add position class before positioning so that position-specific styles are taken into account", function(){
$("head").append('<style> .tooltip.right { white-space: nowrap; } .tooltip.right .tooltip-inner { max-width: none; } </style>')
var container = $("<div />").appendTo("body")
, target = $('<a href="#" rel="tooltip" title="very very very very very very very very long tooltip in one line"></a>')
.appendTo(container)
.tooltip({placement: 'right'})
.tooltip('show')
, tooltip = container.find(".tooltip")
ok( Math.round(target.offset().top + target[0].offsetHeight/2 - tooltip[0].offsetHeight/2) === Math.round(tooltip.offset().top) )
target.tooltip('hide')
})
test("tooltip title test #1", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Simple tooltip" style="display: inline-block; position: absolute; top: 0; left: 0;"></a>')
.appendTo('#qunit-fixture')
.tooltip({
})
.tooltip('show')
equal($('.tooltip').children('.tooltip-inner').text(), 'Simple tooltip', 'title from title attribute is set')
tooltip.tooltip('hide')
ok(!$(".tooltip").length, 'tooltip removed')
})
test("tooltip title test #2", function () {
var tooltip = $('<a href="#" rel="tooltip" title="Simple tooltip" style="display: inline-block; position: absolute; top: 0; left: 0;"></a>')
.appendTo('#qunit-fixture')
.tooltip({
title: 'This is a tooltip with some content'
})
.tooltip('show')
equal($('.tooltip').children('.tooltip-inner').text(), 'Simple tooltip', 'title is set from title attribute while prefered over title option')
tooltip.tooltip('hide')
ok(!$(".tooltip").length, 'tooltip removed')
})
test("tooltip title test #3", function () {
var tooltip = $('<a href="#" rel="tooltip" style="display: inline-block; position: absolute; top: 0; left: 0;"></a>')
.appendTo('#qunit-fixture')
.tooltip({
title: 'This is a tooltip with some content'
})
.tooltip('show')
equal($('.tooltip').children('.tooltip-inner').text(), 'This is a tooltip with some content', 'title from title option is set')
tooltip.tooltip('hide')
ok(!$(".tooltip").length, 'tooltip removed')
})
test("tooltips should be placed dynamically, with the dynamic placement option", function () {
$.support.transition = false
var ttContainer = $('<div id="dynamic-tt-test"/>').css({
'height' : 400
, 'overflow' : 'hidden'
, 'position' : 'absolute'
, 'top' : 0
, 'left' : 0
, 'width' : 600})
.appendTo('body')
var topTooltip = $('<div style="display: inline-block; position: absolute; left: 0; top: 0;" rel="tooltip" title="Top tooltip">Top Dynamic Tooltip</div>')
.appendTo('#dynamic-tt-test')
.tooltip({placement: 'auto'})
.tooltip('show')
ok($(".tooltip").is('.bottom'), 'top positioned tooltip is dynamically positioned bottom')
topTooltip.tooltip('hide')
var rightTooltip = $('<div style="display: inline-block; position: absolute; right: 0;" rel="tooltip" title="Right tooltip">Right Dynamic Tooltip</div>')
.appendTo('#dynamic-tt-test')
.tooltip({placement: 'right auto'})
.tooltip('show')
ok($(".tooltip").is('.left'), 'right positioned tooltip is dynamically positioned left')
rightTooltip.tooltip('hide')
var bottomTooltip = $('<div style="display: inline-block; position: absolute; bottom: 0;" rel="tooltip" title="Bottom tooltip">Bottom Dynamic Tooltip</div>')
.appendTo('#dynamic-tt-test')
.tooltip({placement: 'auto bottom'})
.tooltip('show')
ok($(".tooltip").is('.top'), 'bottom positioned tooltip is dynamically positioned top')
bottomTooltip.tooltip('hide')
var leftTooltip = $('<div style="display: inline-block; position: absolute; left: 0;" rel="tooltip" title="Left tooltip">Left Dynamic Tooltip</div>')
.appendTo('#dynamic-tt-test')
.tooltip({placement: 'auto left'})
.tooltip('show')
ok($(".tooltip").is('.right'), 'left positioned tooltip is dynamically positioned right')
leftTooltip.tooltip('hide')
ttContainer.remove()
})
})

View File

@@ -1,13 +0,0 @@
$(function () {
module("transition")
test("should be defined on jquery support object", function () {
ok($.support.transition !== undefined, 'transition object is defined')
})
test("should provide an end object", function () {
ok($.support.transition ? $.support.transition.end : true, 'end string is defined')
})
})

File diff suppressed because one or more lines are too long

View File

@@ -1,232 +0,0 @@
/**
* QUnit - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}
/** Runoff */
#qunit-fixture {
display:none;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,520 +0,0 @@
/* ========================================================================
* Bootstrap: tooltip.js v3.3.7
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TOOLTIP PUBLIC CLASS DEFINITION
// ===============================
var Tooltip = function (element, options) {
this.type = null
this.options = null
this.enabled = null
this.timeout = null
this.hoverState = null
this.$element = null
this.inState = null
this.init('tooltip', element, options)
}
Tooltip.VERSION = '3.3.7'
Tooltip.TRANSITION_DURATION = 150
Tooltip.DEFAULTS = {
animation: true,
placement: 'top',
selector: false,
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
container: false,
viewport: {
selector: 'body',
padding: 0
}
}
Tooltip.prototype.init = function (type, element, options) {
this.enabled = true
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
this.inState = { click: false, hover: false, focus: false }
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
}
var triggers = this.options.trigger.split(' ')
for (var i = triggers.length; i--;) {
var trigger = triggers[i]
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (trigger != 'manual') {
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
}
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
Tooltip.prototype.getDefaults = function () {
return Tooltip.DEFAULTS
}
Tooltip.prototype.getOptions = function (options) {
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay,
hide: options.delay
}
}
return options
}
Tooltip.prototype.getDelegateOptions = function () {
var options = {}
var defaults = this.getDefaults()
this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value
})
return options
}
Tooltip.prototype.enter = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
}
if (self.tip().hasClass('in') || self.hoverState == 'in') {
self.hoverState = 'in'
return
}
clearTimeout(self.timeout)
self.hoverState = 'in'
if (!self.options.delay || !self.options.delay.show) return self.show()
self.timeout = setTimeout(function () {
if (self.hoverState == 'in') self.show()
}, self.options.delay.show)
}
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true
}
return false
}
Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
}
if (self.isInStateTrue()) return
clearTimeout(self.timeout)
self.hoverState = 'out'
if (!self.options.delay || !self.options.delay.hide) return self.hide()
self.timeout = setTimeout(function () {
if (self.hoverState == 'out') self.hide()
}, self.options.delay.hide)
}
Tooltip.prototype.show = function () {
var e = $.Event('show.bs.' + this.type)
if (this.hasContent() && this.enabled) {
this.$element.trigger(e)
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
if (e.isDefaultPrevented() || !inDom) return
var that = this
var $tip = this.tip()
var tipId = this.getUID(this.type)
this.setContent()
$tip.attr('id', tipId)
this.$element.attr('aria-describedby', tipId)
if (this.options.animation) $tip.addClass('fade')
var placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
var autoToken = /\s?auto?\s?/i
var autoPlace = autoToken.test(placement)
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.addClass(placement)
.data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
this.$element.trigger('inserted.bs.' + this.type)
var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (autoPlace) {
var orgPlacement = placement
var viewportDim = this.getPosition(this.$viewport)
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement
$tip
.removeClass(orgPlacement)
.addClass(placement)
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
this.applyPlacement(calculatedOffset, placement)
var complete = function () {
var prevHoverState = that.hoverState
that.$element.trigger('shown.bs.' + that.type)
that.hoverState = null
if (prevHoverState == 'out') that.leave(that)
}
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete()
}
}
Tooltip.prototype.applyPlacement = function (offset, placement) {
var $tip = this.tip()
var width = $tip[0].offsetWidth
var height = $tip[0].offsetHeight
// manually read margins because getBoundingClientRect includes difference
var marginTop = parseInt($tip.css('margin-top'), 10)
var marginLeft = parseInt($tip.css('margin-left'), 10)
// we must check for NaN for ie 8/9
if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0
offset.top += marginTop
offset.left += marginLeft
// $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0
$.offset.setOffset($tip[0], $.extend({
using: function (props) {
$tip.css({
top: Math.round(props.top),
left: Math.round(props.left)
})
}
}, offset), 0)
$tip.addClass('in')
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight
}
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
if (delta.left) offset.left += delta.left
else offset.top += delta.top
var isVertical = /top|bottom/.test(placement)
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
$tip.offset(offset)
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
}
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
this.arrow()
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isVertical ? 'top' : 'left', '')
}
Tooltip.prototype.setContent = function () {
var $tip = this.tip()
var title = this.getTitle()
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
$tip.removeClass('fade in top bottom left right')
}
Tooltip.prototype.hide = function (callback) {
var that = this
var $tip = $(this.$tip)
var e = $.Event('hide.bs.' + this.type)
function complete() {
if (that.hoverState != 'in') $tip.detach()
if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type)
}
callback && callback()
}
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$tip.removeClass('in')
$.support.transition && $tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete()
this.hoverState = null
return this
}
Tooltip.prototype.fixTitle = function () {
var $e = this.$element
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
Tooltip.prototype.hasContent = function () {
return this.getTitle()
}
Tooltip.prototype.getPosition = function ($element) {
$element = $element || this.$element
var el = $element[0]
var isBody = el.tagName == 'BODY'
var elRect = el.getBoundingClientRect()
if (elRect.width == null) {
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
}
var isSvg = window.SVGElement && el instanceof window.SVGElement
// Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
// See https://github.com/twbs/bootstrap/issues/20280
var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
return $.extend({}, elRect, scroll, outerDims, elOffset)
}
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
}
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
var delta = { top: 0, left: 0 }
if (!this.$viewport) return delta
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
var viewportDimensions = this.getPosition(this.$viewport)
if (/right|left/.test(placement)) {
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
if (topEdgeOffset < viewportDimensions.top) { // top overflow
delta.top = viewportDimensions.top - topEdgeOffset
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
}
} else {
var leftEdgeOffset = pos.left - viewportPadding
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
return delta
}
Tooltip.prototype.getTitle = function () {
var title
var $e = this.$element
var o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title
}
Tooltip.prototype.getUID = function (prefix) {
do prefix += ~~(Math.random() * 1000000)
while (document.getElementById(prefix))
return prefix
}
Tooltip.prototype.tip = function () {
if (!this.$tip) {
this.$tip = $(this.options.template)
if (this.$tip.length != 1) {
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
}
}
return this.$tip
}
Tooltip.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
}
Tooltip.prototype.enable = function () {
this.enabled = true
}
Tooltip.prototype.disable = function () {
this.enabled = false
}
Tooltip.prototype.toggleEnabled = function () {
this.enabled = !this.enabled
}
Tooltip.prototype.toggle = function (e) {
var self = this
if (e) {
self = $(e.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(e.currentTarget, this.getDelegateOptions())
$(e.currentTarget).data('bs.' + this.type, self)
}
}
if (e) {
self.inState.click = !self.inState.click
if (self.isInStateTrue()) self.enter(self)
else self.leave(self)
} else {
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
}
}
Tooltip.prototype.destroy = function () {
var that = this
clearTimeout(this.timeout)
this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type)
if (that.$tip) {
that.$tip.detach()
}
that.$tip = null
that.$arrow = null
that.$viewport = null
that.$element = null
})
}
// TOOLTIP PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tooltip')
var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tooltip
$.fn.tooltip = Plugin
$.fn.tooltip.Constructor = Tooltip
// TOOLTIP NO CONFLICT
// ===================
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old
return this
}
}(jQuery);

View File

@@ -1,59 +0,0 @@
/* ========================================================================
* Bootstrap: transition.js v3.3.7
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd',
MozTransition : 'transitionend',
OTransition : 'oTransitionEnd otransitionend',
transition : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function (duration) {
var called = false
var $el = this
$(this).one('bsTransitionEnd', function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration)
return this
}
$(function () {
$.support.transition = transitionEnd()
if (!$.support.transition) return
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function (e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
}
}
})
}(jQuery);

View File

@@ -1,849 +0,0 @@
// a flag to toggle asset pipeline / compass integration
// defaults to true if twbs-font-path function is present (no function => twbs-font-path('') parsed as string == right side)
// in Sass 3.3 this can be improved with: function-exists(twbs-font-path)
$bootstrap-sass-asset-helper: (twbs-font-path("") != unquote('twbs-font-path("")')) !default;
//
// Variables
// --------------------------------------------------
//== Colors
//
//## Gray and brand colors for use across Bootstrap.
$gray-darker: lighten(#000, 13.5%) !default; // #222
$gray-dark: lighten(#000, 20%) !default; // #333
$gray: lighten(#000, 33.5%) !default; // #555
$gray-light: lighten(#000, 46.7%) !default; // #777
$gray-lighter: lighten(#000, 93.5%) !default; // #eee
$brand-primary: #428bca !default;
$brand-success: #5cb85c !default;
$brand-info: #5bc0de !default;
$brand-warning: #f0ad4e !default;
$brand-danger: #d9534f !default;
//== Scaffolding
//
//## Settings for some of the most global styles.
//** Background color for `<body>`.
$body-bg: #fff !default;
//** Global text color on `<body>`.
$text-color: $gray-dark !default;
//** Global textual link color.
$link-color: $brand-primary !default;
//** Link hover color set via `darken()` function.
$link-hover-color: darken($link-color, 15%) !default;
//== Typography
//
//## Font, line-height, and color for body text, headings, and more.
$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
$font-family-serif: Georgia, "Times New Roman", Times, serif !default;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
$font-family-base: $font-family-sans-serif !default;
$font-size-base: 14px !default;
$font-size-large: ceil(($font-size-base * 1.25)) !default; // ~18px
$font-size-small: ceil(($font-size-base * 0.85)) !default; // ~12px
$font-size-h1: floor(($font-size-base * 2.6)) !default; // ~36px
$font-size-h2: floor(($font-size-base * 2.15)) !default; // ~30px
$font-size-h3: ceil(($font-size-base * 1.7)) !default; // ~24px
$font-size-h4: ceil(($font-size-base * 1.25)) !default; // ~18px
$font-size-h5: $font-size-base !default;
$font-size-h6: ceil(($font-size-base * 0.85)) !default; // ~12px
//** Unit-less `line-height` for use in components like buttons.
$line-height-base: 1.428571429 !default; // 20/14
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
//** By default, this inherits from the `<body>`.
$headings-font-family: inherit !default;
$headings-font-weight: 500 !default;
$headings-line-height: 1.1 !default;
$headings-color: inherit !default;
//== Iconography
//
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
//** Load fonts from this directory.
$icon-font-path: "" !default;
//** File name for all font files.
$icon-font-name: "glyphicons-halflings-regular" !default;
//** Element ID within SVG icon file.
$icon-font-svg-id: "glyphicons_halflingsregular" !default;
//== Components
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
$padding-base-vertical: 6px !default;
$padding-base-horizontal: 12px !default;
$padding-large-vertical: 10px !default;
$padding-large-horizontal: 16px !default;
$padding-small-vertical: 5px !default;
$padding-small-horizontal: 10px !default;
$padding-xs-vertical: 1px !default;
$padding-xs-horizontal: 5px !default;
$line-height-large: 1.33 !default;
$line-height-small: 1.5 !default;
$border-radius-base: 4px !default;
$border-radius-large: 6px !default;
$border-radius-small: 3px !default;
//** Global color for active items (e.g., navs or dropdowns).
$component-active-color: #fff !default;
//** Global background color for active items (e.g., navs or dropdowns).
$component-active-bg: $brand-primary !default;
//** Width of the `border` for generating carets that indicator dropdowns.
$caret-width-base: 4px !default;
//** Carets increase slightly in size for larger components.
$caret-width-large: 5px !default;
//== Tables
//
//## Customizes the `.table` component with basic values, each used across all table variations.
//** Padding for `<th>`s and `<td>`s.
$table-cell-padding: 8px !default;
//** Padding for cells in `.table-condensed`.
$table-condensed-cell-padding: 5px !default;
//** Default background color used for all tables.
$table-bg: transparent !default;
//** Background color used for `.table-striped`.
$table-bg-accent: #f9f9f9 !default;
//** Background color used for `.table-hover`.
$table-bg-hover: #f5f5f5 !default;
$table-bg-active: $table-bg-hover !default;
//** Border color for table and cell borders.
$table-border-color: #ddd !default;
//== Buttons
//
//## For each of Bootstrap's buttons, define text, background and border color.
$btn-font-weight: normal !default;
$btn-default-color: #333 !default;
$btn-default-bg: #fff !default;
$btn-default-border: #ccc !default;
$btn-primary-color: #fff !default;
$btn-primary-bg: $brand-primary !default;
$btn-primary-border: darken($btn-primary-bg, 5%) !default;
$btn-success-color: #fff !default;
$btn-success-bg: $brand-success !default;
$btn-success-border: darken($btn-success-bg, 5%) !default;
$btn-info-color: #fff !default;
$btn-info-bg: $brand-info !default;
$btn-info-border: darken($btn-info-bg, 5%) !default;
$btn-warning-color: #fff !default;
$btn-warning-bg: $brand-warning !default;
$btn-warning-border: darken($btn-warning-bg, 5%) !default;
$btn-danger-color: #fff !default;
$btn-danger-bg: $brand-danger !default;
$btn-danger-border: darken($btn-danger-bg, 5%) !default;
$btn-link-disabled-color: $gray-light !default;
//== Forms
//
//##
//** `<input>` background color
$input-bg: #fff !default;
//** `<input disabled>` background color
$input-bg-disabled: $gray-lighter !default;
//** Text color for `<input>`s
$input-color: $gray !default;
//** `<input>` border color
$input-border: #ccc !default;
//** `<input>` border radius
$input-border-radius: $border-radius-base !default;
//** Border color for inputs on focus
$input-border-focus: #66afe9 !default;
//** Placeholder text color
$input-color-placeholder: $gray-light !default;
//** Default `.form-control` height
$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
//** Large `.form-control` height
$input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
//** Small `.form-control` height
$input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
$legend-color: $gray-dark !default;
$legend-border-color: #e5e5e5 !default;
//** Background color for textual input addons
$input-group-addon-bg: $gray-lighter !default;
//** Border color for textual input addons
$input-group-addon-border-color: $input-border !default;
//== Dropdowns
//
//## Dropdown menu container and contents.
//** Background for the dropdown menu.
$dropdown-bg: #fff !default;
//** Dropdown menu `border-color`.
$dropdown-border: rgba(0,0,0,.15) !default;
//** Dropdown menu `border-color` **for IE8**.
$dropdown-fallback-border: #ccc !default;
//** Divider color for between dropdown items.
$dropdown-divider-bg: #e5e5e5 !default;
//** Dropdown link text color.
$dropdown-link-color: $gray-dark !default;
//** Hover color for dropdown links.
$dropdown-link-hover-color: darken($gray-dark, 5%) !default;
//** Hover background for dropdown links.
$dropdown-link-hover-bg: #f5f5f5 !default;
//** Active dropdown menu item text color.
$dropdown-link-active-color: $component-active-color !default;
//** Active dropdown menu item background color.
$dropdown-link-active-bg: $component-active-bg !default;
//** Disabled dropdown menu item background color.
$dropdown-link-disabled-color: $gray-light !default;
//** Text color for headers within dropdown menus.
$dropdown-header-color: $gray-light !default;
//** Deprecated `$dropdown-caret-color` as of v3.1.0
$dropdown-caret-color: #000 !default;
//-- Z-index master list
//
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
//
// Note: These variables are not generated into the Customizer.
$zindex-navbar: 1000 !default;
$zindex-dropdown: 1000 !default;
$zindex-popover: 1060 !default;
$zindex-tooltip: 1070 !default;
$zindex-navbar-fixed: 1030 !default;
$zindex-modal-background: 1040 !default;
$zindex-modal: 1050 !default;
//== Media queries breakpoints
//
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
// Extra small screen / phone
//** Deprecated `$screen-xs` as of v3.0.1
$screen-xs: 480px !default;
//** Deprecated `$screen-xs-min` as of v3.2.0
$screen-xs-min: $screen-xs !default;
//** Deprecated `$screen-phone` as of v3.0.1
$screen-phone: $screen-xs-min !default;
// Small screen / tablet
//** Deprecated `$screen-sm` as of v3.0.1
$screen-sm: 768px !default;
$screen-sm-min: $screen-sm !default;
//** Deprecated `$screen-tablet` as of v3.0.1
$screen-tablet: $screen-sm-min !default;
// Medium screen / desktop
//** Deprecated `$screen-md` as of v3.0.1
$screen-md: 992px !default;
$screen-md-min: $screen-md !default;
//** Deprecated `$screen-desktop` as of v3.0.1
$screen-desktop: $screen-md-min !default;
// Large screen / wide desktop
//** Deprecated `$screen-lg` as of v3.0.1
$screen-lg: 1200px !default;
$screen-lg-min: $screen-lg !default;
//** Deprecated `$screen-lg-desktop` as of v3.0.1
$screen-lg-desktop: $screen-lg-min !default;
// So media queries don't overlap when required, provide a maximum
$screen-xs-max: ($screen-sm-min - 1) !default;
$screen-sm-max: ($screen-md-min - 1) !default;
$screen-md-max: ($screen-lg-min - 1) !default;
//== Grid system
//
//## Define your custom responsive grid.
//** Number of columns in the grid.
$grid-columns: 12 !default;
//** Padding between columns. Gets divided in half for the left and right.
$grid-gutter-width: 30px !default;
// Navbar collapse
//** Point at which the navbar becomes uncollapsed.
$grid-float-breakpoint: $screen-sm-min !default;
//** Point at which the navbar begins collapsing.
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
$container-tablet: ((720px + $grid-gutter-width)) !default;
//** For `$screen-sm-min` and up.
$container-sm: $container-tablet !default;
// Medium screen / desktop
$container-desktop: ((940px + $grid-gutter-width)) !default;
//** For `$screen-md-min` and up.
$container-md: $container-desktop !default;
// Large screen / wide desktop
$container-large-desktop: ((1140px + $grid-gutter-width)) !default;
//** For `$screen-lg-min` and up.
$container-lg: $container-large-desktop !default;
//== Navbar
//
//##
// Basics of a navbar
$navbar-height: 50px !default;
$navbar-margin-bottom: $line-height-computed !default;
$navbar-border-radius: $border-radius-base !default;
$navbar-padding-horizontal: floor(($grid-gutter-width / 2)) !default;
$navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) !default;
$navbar-collapse-max-height: 340px !default;
$navbar-default-color: #777 !default;
$navbar-default-bg: #f8f8f8 !default;
$navbar-default-border: darken($navbar-default-bg, 6.5%) !default;
// Navbar links
$navbar-default-link-color: #777 !default;
$navbar-default-link-hover-color: #333 !default;
$navbar-default-link-hover-bg: transparent !default;
$navbar-default-link-active-color: #555 !default;
$navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%) !default;
$navbar-default-link-disabled-color: #ccc !default;
$navbar-default-link-disabled-bg: transparent !default;
// Navbar brand label
$navbar-default-brand-color: $navbar-default-link-color !default;
$navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) !default;
$navbar-default-brand-hover-bg: transparent !default;
// Navbar toggle
$navbar-default-toggle-hover-bg: #ddd !default;
$navbar-default-toggle-icon-bar-bg: #888 !default;
$navbar-default-toggle-border-color: #ddd !default;
// Inverted navbar
// Reset inverted navbar basics
$navbar-inverse-color: $gray-light !default;
$navbar-inverse-bg: #222 !default;
$navbar-inverse-border: darken($navbar-inverse-bg, 10%) !default;
// Inverted navbar links
$navbar-inverse-link-color: lighten($gray-light, 20%) !default;
$navbar-inverse-link-hover-color: #fff !default;
$navbar-inverse-link-hover-bg: transparent !default;
$navbar-inverse-link-active-color: $navbar-inverse-link-hover-color !default;
$navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%) !default;
$navbar-inverse-link-disabled-color: #444 !default;
$navbar-inverse-link-disabled-bg: transparent !default;
// Inverted navbar brand label
$navbar-inverse-brand-color: $navbar-inverse-link-color !default;
$navbar-inverse-brand-hover-color: #fff !default;
$navbar-inverse-brand-hover-bg: transparent !default;
// Inverted navbar toggle
$navbar-inverse-toggle-hover-bg: #333 !default;
$navbar-inverse-toggle-icon-bar-bg: #fff !default;
$navbar-inverse-toggle-border-color: #333 !default;
//== Navs
//
//##
//=== Shared nav styles
$nav-link-padding: 10px 15px !default;
$nav-link-hover-bg: $gray-lighter !default;
$nav-disabled-link-color: $gray-light !default;
$nav-disabled-link-hover-color: $gray-light !default;
$nav-open-link-hover-color: #fff !default;
//== Tabs
$nav-tabs-border-color: #ddd !default;
$nav-tabs-link-hover-border-color: $gray-lighter !default;
$nav-tabs-active-link-hover-bg: $body-bg !default;
$nav-tabs-active-link-hover-color: $gray !default;
$nav-tabs-active-link-hover-border-color: #ddd !default;
$nav-tabs-justified-link-border-color: #ddd !default;
$nav-tabs-justified-active-link-border-color: $body-bg !default;
//== Pills
$nav-pills-border-radius: $border-radius-base !default;
$nav-pills-active-link-hover-bg: $component-active-bg !default;
$nav-pills-active-link-hover-color: $component-active-color !default;
//== Pagination
//
//##
$pagination-color: $link-color !default;
$pagination-bg: #fff !default;
$pagination-border: #ddd !default;
$pagination-hover-color: $link-hover-color !default;
$pagination-hover-bg: $gray-lighter !default;
$pagination-hover-border: #ddd !default;
$pagination-active-color: #fff !default;
$pagination-active-bg: $brand-primary !default;
$pagination-active-border: $brand-primary !default;
$pagination-disabled-color: $gray-light !default;
$pagination-disabled-bg: #fff !default;
$pagination-disabled-border: #ddd !default;
//== Pager
//
//##
$pager-bg: $pagination-bg !default;
$pager-border: $pagination-border !default;
$pager-border-radius: 15px !default;
$pager-hover-bg: $pagination-hover-bg !default;
$pager-active-bg: $pagination-active-bg !default;
$pager-active-color: $pagination-active-color !default;
$pager-disabled-color: $pagination-disabled-color !default;
//== Jumbotron
//
//##
$jumbotron-padding: 30px !default;
$jumbotron-color: inherit !default;
$jumbotron-bg: $gray-lighter !default;
$jumbotron-heading-color: inherit !default;
$jumbotron-font-size: ceil(($font-size-base * 1.5)) !default;
//== Form states and alerts
//
//## Define colors for form feedback states and, by default, alerts.
$state-success-text: #3c763d !default;
$state-success-bg: #dff0d8 !default;
$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%) !default;
$state-info-text: #31708f !default;
$state-info-bg: #d9edf7 !default;
$state-info-border: darken(adjust-hue($state-info-bg, -10), 7%) !default;
$state-warning-text: #8a6d3b !default;
$state-warning-bg: #fcf8e3 !default;
$state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%) !default;
$state-danger-text: #a94442 !default;
$state-danger-bg: #f2dede !default;
$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default;
//== Tooltips
//
//##
//** Tooltip max width
$tooltip-max-width: 200px !default;
//** Tooltip text color
$tooltip-color: #fff !default;
//** Tooltip background color
$tooltip-bg: #000 !default;
$tooltip-opacity: .9 !default;
//** Tooltip arrow width
$tooltip-arrow-width: 5px !default;
//** Tooltip arrow color
$tooltip-arrow-color: $tooltip-bg !default;
//== Popovers
//
//##
//** Popover body background color
$popover-bg: #fff !default;
//** Popover maximum width
$popover-max-width: 276px !default;
//** Popover border color
$popover-border-color: rgba(0,0,0,.2) !default;
//** Popover fallback border color
$popover-fallback-border-color: #ccc !default;
//** Popover title background color
$popover-title-bg: darken($popover-bg, 3%) !default;
//** Popover arrow width
$popover-arrow-width: 10px !default;
//** Popover arrow color
$popover-arrow-color: #fff !default;
//** Popover outer arrow width
$popover-arrow-outer-width: ($popover-arrow-width + 1) !default;
//** Popover outer arrow color
$popover-arrow-outer-color: fade_in($popover-border-color, 0.05) !default;
//** Popover outer arrow fallback color
$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default;
//== Labels
//
//##
//** Default label background color
$label-default-bg: $gray-light !default;
//** Primary label background color
$label-primary-bg: $brand-primary !default;
//** Success label background color
$label-success-bg: $brand-success !default;
//** Info label background color
$label-info-bg: $brand-info !default;
//** Warning label background color
$label-warning-bg: $brand-warning !default;
//** Danger label background color
$label-danger-bg: $brand-danger !default;
//** Default label text color
$label-color: #fff !default;
//** Default text color of a linked label
$label-link-hover-color: #fff !default;
//== Modals
//
//##
//** Padding applied to the modal body
$modal-inner-padding: 15px !default;
//** Padding applied to the modal title
$modal-title-padding: 15px !default;
//** Modal title line-height
$modal-title-line-height: $line-height-base !default;
//** Background color of modal content area
$modal-content-bg: #fff !default;
//** Modal content border color
$modal-content-border-color: rgba(0,0,0,.2) !default;
//** Modal content border color **for IE8**
$modal-content-fallback-border-color: #999 !default;
//** Modal backdrop background color
$modal-backdrop-bg: #000 !default;
//** Modal backdrop opacity
$modal-backdrop-opacity: .5 !default;
//** Modal header border color
$modal-header-border-color: #e5e5e5 !default;
//** Modal footer border color
$modal-footer-border-color: $modal-header-border-color !default;
$modal-lg: 900px !default;
$modal-md: 600px !default;
$modal-sm: 300px !default;
//== Alerts
//
//## Define alert colors, border radius, and padding.
$alert-padding: 15px !default;
$alert-border-radius: $border-radius-base !default;
$alert-link-font-weight: bold !default;
$alert-success-bg: $state-success-bg !default;
$alert-success-text: $state-success-text !default;
$alert-success-border: $state-success-border !default;
$alert-info-bg: $state-info-bg !default;
$alert-info-text: $state-info-text !default;
$alert-info-border: $state-info-border !default;
$alert-warning-bg: $state-warning-bg !default;
$alert-warning-text: $state-warning-text !default;
$alert-warning-border: $state-warning-border !default;
$alert-danger-bg: $state-danger-bg !default;
$alert-danger-text: $state-danger-text !default;
$alert-danger-border: $state-danger-border !default;
//== Progress bars
//
//##
//** Background color of the whole progress component
$progress-bg: #f5f5f5 !default;
//** Progress bar text color
$progress-bar-color: #fff !default;
//** Default progress bar color
$progress-bar-bg: $brand-primary !default;
//** Success progress bar color
$progress-bar-success-bg: $brand-success !default;
//** Warning progress bar color
$progress-bar-warning-bg: $brand-warning !default;
//** Danger progress bar color
$progress-bar-danger-bg: $brand-danger !default;
//** Info progress bar color
$progress-bar-info-bg: $brand-info !default;
//== List group
//
//##
//** Background color on `.list-group-item`
$list-group-bg: #fff !default;
//** `.list-group-item` border color
$list-group-border: #ddd !default;
//** List group border radius
$list-group-border-radius: $border-radius-base !default;
//** Background color of single list items on hover
$list-group-hover-bg: #f5f5f5 !default;
//** Text color of active list items
$list-group-active-color: $component-active-color !default;
//** Background color of active list items
$list-group-active-bg: $component-active-bg !default;
//** Border color of active list elements
$list-group-active-border: $list-group-active-bg !default;
//** Text color for content within active list items
$list-group-active-text-color: lighten($list-group-active-bg, 40%) !default;
//** Text color of disabled list items
$list-group-disabled-color: $gray-light !default;
//** Background color of disabled list items
$list-group-disabled-bg: $gray-lighter !default;
//** Text color for content within disabled list items
$list-group-disabled-text-color: $list-group-disabled-color !default;
$list-group-link-color: #555 !default;
$list-group-link-hover-color: $list-group-link-color !default;
$list-group-link-heading-color: #333 !default;
//== Panels
//
//##
$panel-bg: #fff !default;
$panel-body-padding: 15px !default;
$panel-heading-padding: 10px 15px !default;
$panel-footer-padding: $panel-heading-padding !default;
$panel-border-radius: $border-radius-base !default;
//** Border color for elements within panels
$panel-inner-border: #ddd !default;
$panel-footer-bg: #f5f5f5 !default;
$panel-default-text: $gray-dark !default;
$panel-default-border: #ddd !default;
$panel-default-heading-bg: #f5f5f5 !default;
$panel-primary-text: #fff !default;
$panel-primary-border: $brand-primary !default;
$panel-primary-heading-bg: $brand-primary !default;
$panel-success-text: $state-success-text !default;
$panel-success-border: $state-success-border !default;
$panel-success-heading-bg: $state-success-bg !default;
$panel-info-text: $state-info-text !default;
$panel-info-border: $state-info-border !default;
$panel-info-heading-bg: $state-info-bg !default;
$panel-warning-text: $state-warning-text !default;
$panel-warning-border: $state-warning-border !default;
$panel-warning-heading-bg: $state-warning-bg !default;
$panel-danger-text: $state-danger-text !default;
$panel-danger-border: $state-danger-border !default;
$panel-danger-heading-bg: $state-danger-bg !default;
//== Thumbnails
//
//##
//** Padding around the thumbnail image
$thumbnail-padding: 4px !default;
//** Thumbnail background color
$thumbnail-bg: $body-bg !default;
//** Thumbnail border color
$thumbnail-border: #ddd !default;
//** Thumbnail border radius
$thumbnail-border-radius: $border-radius-base !default;
//** Custom text color for thumbnail captions
$thumbnail-caption-color: $text-color !default;
//** Padding around the thumbnail caption
$thumbnail-caption-padding: 9px !default;
//== Wells
//
//##
$well-bg: #f5f5f5 !default;
$well-border: darken($well-bg, 7%) !default;
//== Badges
//
//##
$badge-color: #fff !default;
//** Linked badge text color on hover
$badge-link-hover-color: #fff !default;
$badge-bg: $gray-light !default;
//** Badge text color in active nav link
$badge-active-color: $link-color !default;
//** Badge background color in active nav link
$badge-active-bg: #fff !default;
$badge-font-weight: bold !default;
$badge-line-height: 1 !default;
$badge-border-radius: 10px !default;
//== Breadcrumbs
//
//##
$breadcrumb-padding-vertical: 8px !default;
$breadcrumb-padding-horizontal: 15px !default;
//** Breadcrumb background color
$breadcrumb-bg: #f5f5f5 !default;
//** Breadcrumb text color
$breadcrumb-color: #ccc !default;
//** Text color of current page in the breadcrumb
$breadcrumb-active-color: $gray-light !default;
//** Textual separator for between breadcrumb elements
$breadcrumb-separator: "/" !default;
//== Carousel
//
//##
$carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6) !default;
$carousel-control-color: #fff !default;
$carousel-control-width: 15% !default;
$carousel-control-opacity: .5 !default;
$carousel-control-font-size: 20px !default;
$carousel-indicator-active-bg: #fff !default;
$carousel-indicator-border-color: #fff !default;
$carousel-caption-color: #fff !default;
//== Close
//
//##
$close-font-weight: bold !default;
$close-color: #000 !default;
$close-text-shadow: 0 1px 0 #fff !default;
//== Code
//
//##
$code-color: #c7254e !default;
$code-bg: #f9f2f4 !default;
$kbd-color: #fff !default;
$kbd-bg: #333 !default;
$pre-bg: #f5f5f5 !default;
$pre-color: $gray-dark !default;
$pre-border-color: #ccc !default;
$pre-scrollable-max-height: 340px !default;
//== Type
//
//##
//** Horizontal offset for forms and lists.
$component-offset-horizontal: 180px !default;
//** Text muted color
$text-muted: $gray-light !default;
//** Abbreviations and acronyms border color
$abbr-border-color: $gray-light !default;
//** Headings small color
$headings-small-color: $gray-light !default;
//** Blockquote small color
$blockquote-small-color: $gray-light !default;
//** Blockquote font size
$blockquote-font-size: ($font-size-base * 1.25) !default;
//** Blockquote border color
$blockquote-border-color: $gray-lighter !default;
//** Page header border color
$page-header-border-color: $gray-lighter !default;
//** Width of horizontal description list titles
$dl-horizontal-offset: $component-offset-horizontal !default;
//** Horizontal line color.
$hr-border: $gray-lighter !default;

View File

@@ -1,5 +0,0 @@
/* Welcome to Compass. Use this file to write IE specific override styles.
* Import this file using the following HTML or equivalent:
* <!--[if IE]>
* <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
* <![endif]--> */

View File

@@ -1,60 +0,0 @@
/*
* jQuery UI Bootstrap v1.0 Alpha
*
* jQuery UI Accordion 1.10.3
* http://jqueryui.com/accordion/
*
* Portions copyright Addy Osmani, jQuery UI .ui-accordion Twitter Bootstrap
* Created the LESS version by @dharapvj
* Released under MIT
*/
/* IE/Win - Fix animation bug - #4615 */
.ui-accordion {
width: 100%;
.ui-accordion-li-fix {
display: inline;
}
.ui-accordion-header-active {
border-bottom: 0 !important;
}
.ui-accordion-header {
display: block;
cursor: pointer;
position: relative;
margin-top: 2px;
padding: .5em .5em .5em .7em;
min-height: 0; /* support: IE7 */
}
.ui-accordion-icons {
padding-left: 2.2em;
}
.ui-accordion-noicons {
padding-left: .7em;
}
.ui-accordion-icons {
.ui-accordion-icons{
padding-left: 2.2em;
}
}
.ui-accordion-header {
.ui-accordion-header-icon {
position: absolute;
left: .5em;
top: 50%;
margin-top: -8px;
}
}
.ui-accordion-content {
padding: 1em 2.2em;
border-top: 0;
margin-top: -2px;
position: relative;
top: 1px;
margin-bottom: 2px;
overflow: auto;
display: none;
}
.ui-accordion-content-active {
display: block;
}
}

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