mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-03-05 11:38:25 +00:00
Compare commits
35 Commits
checkin
...
5174a442bc
| Author | SHA1 | Date | |
|---|---|---|---|
| 5174a442bc | |||
| 5f1fd59dd2 | |||
| 3be2a9f4b5 | |||
| 883ef4ed8b | |||
| 2195a60438 | |||
| 12c4b63947 | |||
| 39ab9df836 | |||
| a99e4b1d1c | |||
| 18a091f8ea | |||
| d64d0f54d4 | |||
| 8f54897b69 | |||
| 97830596b5 | |||
| 68f401097d | |||
| 8c5b2f426d | |||
| e3fc05772e | |||
| e38205b9e7 | |||
| f2b9642772 | |||
| 08939a0e1f | |||
| 15b7f9c7c1 | |||
| c67f7e1e54 | |||
| 4f268b3168 | |||
| 0e7723828a | |||
| c46a2c53f3 | |||
| b3617a74bf | |||
| 5806cbc72d | |||
| ab2ff9f146 | |||
| fd5575a818 | |||
| 402a1dd7f0 | |||
| be7688aa75 | |||
| a472e414f7 | |||
| 618c02aa9c | |||
| 3056b6ef71 | |||
| 548c960996 | |||
| 2cc43dcdb7 | |||
| 6db25fb56b |
@@ -6,3 +6,4 @@ plugins =
|
||||
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/tests/*
|
||||
|
||||
45
.github/workflows/django.yml
vendored
45
.github/workflows/django.yml
vendored
@@ -9,22 +9,25 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
IMGUR_UPLOAD_CLIENT_ID: ${{ secrets.IMGUR_UPLOAD_CLIENT_ID }}
|
||||
IMGUR_UPLOAD_CLIENT_SECRET: ${{ secrets.IMGUR_UPLOAD_CLIENT_SECRET }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
test-group: ["RIGS", "versioning", "users", "assets"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: 3.9
|
||||
- name: Cache python deps
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ env.pythonLocation }}-${{ hashFiles('requirements.txt') }}
|
||||
- name: Setup Chromedriver
|
||||
if: \!${{ matrix.parallel }}
|
||||
run: |
|
||||
wget https://chromedriver.storage.googleapis.com/2.36/chromedriver_linux64.zip
|
||||
unzip chromedriver_linux64.zip
|
||||
@@ -35,7 +38,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pycodestyle coverage coveralls django_coverage_plugin
|
||||
pip install -r requirements.txt
|
||||
pip install --upgrade --upgrade-strategy eager -r requirements.txt
|
||||
python manage.py collectstatic --noinput
|
||||
- name: Basic Checks
|
||||
run: |
|
||||
@@ -43,6 +46,28 @@ jobs:
|
||||
python manage.py check
|
||||
python manage.py makemigrations --check --dry-run
|
||||
- name: Run Tests
|
||||
run: |
|
||||
coverage run manage.py test --verbosity=2
|
||||
coveralls --service=github
|
||||
run: coverage run -p -m pytest --cov=${{ matrix.test-group }} --cov-append -n 8 ${{ matrix.test-group }}/tests/
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: failure-screenshots ${{ matrix.test-group }}
|
||||
path: screenshots/
|
||||
retention-days: 5
|
||||
- name: Upload Coverage
|
||||
run: coverage combine && coveralls --service=github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_FLAG_NAME: ${{ matrix.test-group }}
|
||||
COVERALLS_PARALLEL: true
|
||||
coveralls:
|
||||
name: Indicate completion to coveralls.io
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
container: python:3-slim
|
||||
steps:
|
||||
- name: Finished
|
||||
run: |
|
||||
pip3 install --upgrade coveralls
|
||||
coveralls --service=github --finish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
PyRIGS
|
||||
5
.idea/encodings.xml
generated
5
.idea/encodings.xml
generated
@@ -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
8
.idea/modules.xml
generated
@@ -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>
|
||||
5
.idea/scopes/scope_settings.xml
generated
5
.idea/scopes/scope_settings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<state>
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/vcs.xml
generated
7
.idea/vcs.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
1156
.rubocop.yml
1156
.rubocop.yml
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
*.sqlite3
|
||||
*.scss
|
||||
*.md
|
||||
*.rb
|
||||
Vagrantfile
|
||||
config/vagrant/*
|
||||
config/vagrant.yml
|
||||
|
||||
@@ -263,6 +263,3 @@ USE_GRAVATAR = True
|
||||
|
||||
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
||||
|
||||
IMGUR_UPLOAD_CLIENT_ID = env('IMGUR_UPLOAD_CLIENT_ID', default="")
|
||||
IMGUR_UPLOAD_CLIENT_SECRET = env('IMGUR_UPLOAD_CLIENT_SECRET', default="")
|
||||
|
||||
@@ -6,7 +6,6 @@ import os
|
||||
import pytz
|
||||
from datetime import date, time, datetime, timedelta
|
||||
from django.conf import settings
|
||||
import imgurpython
|
||||
import PyRIGS.settings
|
||||
import sys
|
||||
import pathlib
|
||||
@@ -21,9 +20,6 @@ def create_datetime(year, month, day, hour, min):
|
||||
def create_browser():
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument("--window-size=1920,1080")
|
||||
# No caching, please and thank you
|
||||
options.add_argument("--aggressive-cache-discard")
|
||||
options.add_argument("--disk-cache-size=0")
|
||||
options.add_argument("--headless")
|
||||
if settings.CI:
|
||||
options.add_argument("--no-sandbox")
|
||||
@@ -62,19 +58,7 @@ def screenshot_failure(func):
|
||||
if not pathlib.Path("screenshots").is_dir():
|
||||
os.mkdir("screenshots")
|
||||
self.driver.save_screenshot(screenshot_file)
|
||||
|
||||
if settings.IMGUR_UPLOAD_CLIENT_ID != "":
|
||||
config = {
|
||||
'album': None,
|
||||
'name': screenshot_name,
|
||||
'title': screenshot_name,
|
||||
'description': ""
|
||||
}
|
||||
client = imgurpython.ImgurClient(settings.IMGUR_UPLOAD_CLIENT_ID, settings.IMGUR_UPLOAD_CLIENT_SECRET)
|
||||
image = client.upload_from_path(screenshot_file, config=config)
|
||||
print("Error in test {} is at url {}".format(screenshot_name, image['link']), file=sys.stderr)
|
||||
else:
|
||||
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||
raise e
|
||||
return wrapper_func
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.urls import path
|
||||
from django.conf.urls import include, url
|
||||
from django.urls import path, re_path
|
||||
from django.conf.urls import include
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@@ -39,6 +39,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
|
||||
|
||||
@@ -24,7 +24,12 @@ from django.views.decorators.cache import never_cache, cache_page
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
|
||||
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'
|
||||
|
||||
@@ -151,7 +156,7 @@ class SecureAPIRequest(generic.View):
|
||||
|
||||
class ModalURLMixin:
|
||||
def get_close_url(self, update, detail):
|
||||
if self.request.is_ajax():
|
||||
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]))
|
||||
@@ -170,7 +175,7 @@ class GenericListView(generic.ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericListView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = self.model.__name__ + "s"
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
@@ -202,7 +207,7 @@ class GenericDetailView(generic.DetailView):
|
||||
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 self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
@@ -213,7 +218,7 @@ class GenericUpdateView(generic.UpdateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Edit {}".format(self.model.__name__)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
@@ -224,7 +229,7 @@ class GenericCreateView(generic.CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Create {}".format(self.model.__name__)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
4
RIGS/static/css/bootstrap-select.css
vendored
4
RIGS/static/css/bootstrap-select.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap alert.js v4.5.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Bootstrap alert.js v4.6.0 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./util.js")):"function"==typeof define&&define.amd?define(["jquery","./util.js"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Alert=t(e.jQuery,e.Util)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e,t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t;var r=e.fn.alert,o=function(){function r(e){this._element=e}var o,l,i,a=r.prototype;return a.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},a.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},a._getRootElement=function(n){var r=t.getSelectorFromElement(n),o=!1;return r&&(o=document.querySelector(r)),o||(o=e(n).closest(".alert")[0]),o},a._triggerCloseEvent=function(t){var n=e.Event("close.bs.alert");return e(t).trigger(n),n},a._removeElement=function(n){var r=this;if(e(n).removeClass("show"),e(n).hasClass("fade")){var o=t.getTransitionDurationFromElement(n);e(n).one(t.TRANSITION_END,(function(e){return r._destroyElement(n,e)})).emulateTransitionEnd(o)}else this._destroyElement(n)},a._destroyElement=function(t){e(t).detach().trigger("closed.bs.alert").remove()},r._jQueryInterface=function(t){return this.each((function(){var n=e(this),o=n.data("bs.alert");o||(o=new r(this),n.data("bs.alert",o)),"close"===t&&o[t](this)}))},r._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},o=r,i=[{key:"VERSION",get:function(){return"4.5.2"}}],(l=null)&&n(o.prototype,l),i&&n(o,i),r}();return e(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',o._handleDismiss(new o)),e.fn.alert=o._jQueryInterface,e.fn.alert.Constructor=o,e.fn.alert.noConflict=function(){return e.fn.alert=r,o._jQueryInterface},o}));
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./util.js")):"function"==typeof define&&define.amd?define(["jquery","./util"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Alert=t(e.jQuery,e.Util)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e),l=n(t);function u(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}var a="bs.alert",o=r.default.fn.alert,i=function(){function e(e){this._element=e}var t,n,o,i=e.prototype;return i.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},i.dispose=function(){r.default.removeData(this._element,a),this._element=null},i._getRootElement=function(e){var t=l.default.getSelectorFromElement(e),n=!1;return t&&(n=document.querySelector(t)),n||(n=r.default(e).closest(".alert")[0]),n},i._triggerCloseEvent=function(e){var t=r.default.Event("close.bs.alert");return r.default(e).trigger(t),t},i._removeElement=function(e){var t=this;if(r.default(e).removeClass("show"),r.default(e).hasClass("fade")){var n=l.default.getTransitionDurationFromElement(e);r.default(e).one(l.default.TRANSITION_END,(function(n){return t._destroyElement(e,n)})).emulateTransitionEnd(n)}else this._destroyElement(e)},i._destroyElement=function(e){r.default(e).detach().trigger("closed.bs.alert").remove()},e._jQueryInterface=function(t){return this.each((function(){var n=r.default(this),l=n.data(a);l||(l=new e(this),n.data(a,l)),"close"===t&&l[t](this)}))},e._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},t=e,o=[{key:"VERSION",get:function(){return"4.6.0"}}],(n=null)&&u(t.prototype,n),o&&u(t,o),e}();return r.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',i._handleDismiss(new i)),r.default.fn.alert=i._jQueryInterface,r.default.fn.alert.Constructor=i,r.default.fn.alert.noConflict=function(){return r.default.fn.alert=o,i._jQueryInterface},i}));
|
||||
File diff suppressed because one or more lines are too long
4
RIGS/static/js/bootstrap-select.js
vendored
4
RIGS/static/js/bootstrap-select.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
RIGS/static/js/flatpickr.min.js
vendored
4
RIGS/static/js/flatpickr.min.js
vendored
File diff suppressed because one or more lines are too long
24
RIGS/static/js/jquery-ui.js
vendored
24
RIGS/static/js/jquery-ui.js
vendored
File diff suppressed because one or more lines are too long
4
RIGS/static/js/jquery.js
vendored
4
RIGS/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
4
RIGS/static/js/main.min.js
vendored
4
RIGS/static/js/main.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap popover.js v4.5.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Bootstrap popover.js v4.6.0 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery"),require("./tooltip.js")):"function"==typeof define&&define.amd?define(["jquery","./tooltip.js"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Popover=e(t.jQuery,t.Tooltip)}(this,(function(t,e){"use strict";function n(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function o(){return(o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t[o]=n[o])}return t}).apply(this,arguments)}t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t,e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e;var r="popover",i=".bs.popover",s=t.fn[r],p=new RegExp("(^|\\s)bs-popover\\S+","g"),u=o({},e.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),a=o({},e.DefaultType,{content:"(string|element|function)"}),l={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},c=function(e){var o,s;function c(){return e.apply(this,arguments)||this}s=e,(o=c).prototype=Object.create(s.prototype),o.prototype.constructor=o,o.__proto__=s;var f,h,d,y=c.prototype;return y.isWithContent=function(){return this.getTitle()||this._getContent()},y.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},y.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},y.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(".popover-header"),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(".popover-body"),n),e.removeClass("fade show")},y._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},y._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(p);null!==n&&n.length>0&&e.removeClass(n.join(""))},c._jQueryInterface=function(e){return this.each((function(){var n=t(this).data("bs.popover"),o="object"==typeof e?e:null;if((n||!/dispose|hide/.test(e))&&(n||(n=new c(this,o),t(this).data("bs.popover",n)),"string"==typeof e)){if(void 0===n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},f=c,d=[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return r}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return l}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return a}}],(h=null)&&n(f.prototype,h),d&&n(f,d),c}(e);return t.fn[r]=c._jQueryInterface,t.fn[r].Constructor=c,t.fn[r].noConflict=function(){return t.fn[r]=s,c._jQueryInterface},c}));
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery"),require("./tooltip.js")):"function"==typeof define&&define.amd?define(["jquery","./tooltip"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Popover=e(t.jQuery,t.Tooltip)}(this,(function(t,e){"use strict";function n(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=n(t),r=n(e);function i(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function u(){return(u=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t[o]=n[o])}return t}).apply(this,arguments)}var f="popover",a="bs.popover",l="."+a,s=o.default.fn[f],c=new RegExp("(^|\\s)bs-popover\\S+","g"),p=u({},r.default.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),d=u({},r.default.DefaultType,{content:"(string|element|function)"}),h={HIDE:"hide"+l,HIDDEN:"hidden"+l,SHOW:"show"+l,SHOWN:"shown"+l,INSERTED:"inserted"+l,CLICK:"click"+l,FOCUSIN:"focusin"+l,FOCUSOUT:"focusout"+l,MOUSEENTER:"mouseenter"+l,MOUSELEAVE:"mouseleave"+l},y=function(t){var e,n;function r(){return t.apply(this,arguments)||this}n=t,(e=r).prototype=Object.create(n.prototype),e.prototype.constructor=e,e.__proto__=n;var u,s,y,v=r.prototype;return v.isWithContent=function(){return this.getTitle()||this._getContent()},v.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},v.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},v.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},v._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},v._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(c);null!==e&&e.length>0&&t.removeClass(e.join(""))},r._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(a),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new r(this,n),o.default(this).data(a,e)),"string"==typeof t)){if(void 0===e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},u=r,y=[{key:"VERSION",get:function(){return"4.6.0"}},{key:"Default",get:function(){return p}},{key:"NAME",get:function(){return f}},{key:"DATA_KEY",get:function(){return a}},{key:"Event",get:function(){return h}},{key:"EVENT_KEY",get:function(){return l}},{key:"DefaultType",get:function(){return d}}],(s=null)&&i(u.prototype,s),y&&i(u,y),r}(r.default);return o.default.fn[f]=y._jQueryInterface,o.default.fn[f].Constructor=y,o.default.fn[f].noConflict=function(){return o.default.fn[f]=s,y._jQueryInterface},y}));
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap util.js v4.5.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Bootstrap util.js v4.6.0 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Util=e(t.jQuery)}(this,(function(t){"use strict";t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t;function e(e){var r=this,o=!1;return t(this).one(n.TRANSITION_END,(function(){o=!0})),setTimeout((function(){o||n.triggerTransitionEnd(r)}),e),this}var n={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(t){var e=t.getAttribute("data-target");if(!e||"#"===e){var n=t.getAttribute("href");e=n&&"#"!==n?n.trim():""}try{return document.querySelector(e)?e:null}catch(t){return null}},getTransitionDurationFromElement:function(e){if(!e)return 0;var n=t(e).css("transition-duration"),r=t(e).css("transition-delay"),o=parseFloat(n),i=parseFloat(r);return o||i?(n=n.split(",")[0],r=r.split(",")[0],1e3*(parseFloat(n)+parseFloat(r))):0},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(e){t(e).trigger("transitionend")},supportsTransitionEnd:function(){return Boolean("transitionend")},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,r){for(var o in r)if(Object.prototype.hasOwnProperty.call(r,o)){var i=r[o],a=e[o],u=a&&n.isElement(a)?"element":null==(s=a)?""+s:{}.toString.call(s).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(i).test(u))throw new Error(t.toUpperCase()+': Option "'+o+'" provided type "'+u+'" but expected type "'+i+'".')}var s},findShadowRoot:function(t){if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){var e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?n.findShadowRoot(t.parentNode):null},jQueryDetection:function(){if(void 0===t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};return n.jQueryDetection(),t.fn.emulateTransitionEnd=e,t.event.special[n.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}},n}));
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Util=e(t.jQuery)}(this,(function(t){"use strict";function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var n=e(t),r="transitionend";function o(t){var e=this,r=!1;return n.default(this).one(i.TRANSITION_END,(function(){r=!0})),setTimeout((function(){r||i.triggerTransitionEnd(e)}),t),this}var i={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(t){var e=t.getAttribute("data-target");if(!e||"#"===e){var n=t.getAttribute("href");e=n&&"#"!==n?n.trim():""}try{return document.querySelector(e)?e:null}catch(t){return null}},getTransitionDurationFromElement:function(t){if(!t)return 0;var e=n.default(t).css("transition-duration"),r=n.default(t).css("transition-delay"),o=parseFloat(e),i=parseFloat(r);return o||i?(e=e.split(",")[0],r=r.split(",")[0],1e3*(parseFloat(e)+parseFloat(r))):0},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(t){n.default(t).trigger(r)},supportsTransitionEnd:function(){return Boolean(r)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var r in n)if(Object.prototype.hasOwnProperty.call(n,r)){var o=n[r],a=e[r],u=a&&i.isElement(a)?"element":null==(l=a)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(o).test(u))throw new Error(t.toUpperCase()+': Option "'+r+'" provided type "'+u+'" but expected type "'+o+'".')}var l},findShadowRoot:function(t){if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){var e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?i.findShadowRoot(t.parentNode):null},jQueryDetection:function(){if(void 0===n.default)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var t=n.default.fn.jquery.split(" ")[0].split(".");if(t[0]<2&&t[1]<9||1===t[0]&&9===t[1]&&t[2]<1||t[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};return i.jQueryDetection(),n.default.fn.emulateTransitionEnd=o,n.default.event.special[i.TRANSITION_END]={bindType:r,delegateType:r,handle:function(t){if(n.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}},i}));
|
||||
@@ -304,3 +304,43 @@ class UserPage(BasePage):
|
||||
|
||||
def generate_key(self):
|
||||
self.find_element(*self._generation_button_selector).click()
|
||||
|
||||
|
||||
class CalendarPage(BasePage):
|
||||
URL_TEMPLATE = 'rigboard/calendar'
|
||||
|
||||
_go_locator = (By.ID, 'go-to-date-button')
|
||||
|
||||
_today_selector = (By.ID, 'today-button')
|
||||
|
||||
_prev_selector = (By.ID, 'prev-button')
|
||||
_next_selector = (By.ID, 'next-button')
|
||||
|
||||
_month_selector = (By.ID, 'month-button')
|
||||
_week_selector = (By.ID, 'week-button')
|
||||
_day_selector = (By.ID, 'day-button')
|
||||
|
||||
def go(self):
|
||||
return self.find_element(*self._go_locator).click()
|
||||
|
||||
@property
|
||||
def target_date(self):
|
||||
return regions.DatePicker(self, self.find_element(By.ID, 'go-to-date-input'))
|
||||
|
||||
def today(self):
|
||||
return self.find_element(*self._today_selector).click()
|
||||
|
||||
def prev(self):
|
||||
return self.find_element(*self._prev_selector).click()
|
||||
|
||||
def next(self):
|
||||
return self.find_element(*self._next_selector).click()
|
||||
|
||||
def month(self):
|
||||
return self.find_element(*self._month_selector).click()
|
||||
|
||||
def week(self):
|
||||
return self.find_element(*self._week_selector).click()
|
||||
|
||||
def day(self):
|
||||
return self.find_element(*self._day_selector).click()
|
||||
|
||||
@@ -645,7 +645,18 @@ class TestCalendar(BaseRigboardTest):
|
||||
else:
|
||||
self.assertNotContains(response, "TE E" + str(test) + " ")
|
||||
|
||||
# Wow - that was a lot of tests
|
||||
def test_calendar_buttons(self): # If FullCalendar fails to load for whatever reason, the buttons don't work
|
||||
self.page = pages.CalendarPage(self.driver, self.live_server_url).open()
|
||||
self.assertIn(timezone.now().strftime("%Y-%m"), self.driver.current_url)
|
||||
|
||||
target_date = datetime.date(2020, 1, 1)
|
||||
self.page.target_date.set_value(target_date)
|
||||
self.page.go()
|
||||
self.assertIn(self.page.target_date.value.strftime("%Y-%m"), self.driver.current_url)
|
||||
|
||||
self.page.next()
|
||||
target_date += datetime.timedelta(days=32)
|
||||
self.assertIn(target_date.strftime("%m"), self.driver.current_url)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
|
||||
18
RIGS/urls.py
18
RIGS/urls.py
@@ -1,6 +1,6 @@
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import path
|
||||
from django.urls import path, re_path
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.generic import RedirectView
|
||||
from PyRIGS.decorators import (api_key_required, has_oembed,
|
||||
@@ -45,10 +45,10 @@ urlpatterns = [
|
||||
path('rigboard/', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
||||
path('rigboard/calendar/', login_required()(rigboard.WebCalendar.as_view()),
|
||||
name='web_calendar'),
|
||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
||||
|
||||
|
||||
@@ -130,12 +130,12 @@ urlpatterns = [
|
||||
path('event/<int:pk>/auth/preview/',
|
||||
permission_required_with_403('RIGS.change_event')(rigboard.EventAuthoriseRequestEmailPreview.as_view()),
|
||||
name='event_authorise_preview'),
|
||||
url(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
||||
name='event_authorise'),
|
||||
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
||||
name='event_authorise'),
|
||||
|
||||
# ICS Calendar - API key authentication
|
||||
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
||||
name="ics_calendar"),
|
||||
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
||||
name="ics_calendar"),
|
||||
|
||||
|
||||
# Legacy URLs
|
||||
|
||||
134
Vagrantfile
vendored
134
Vagrantfile
vendored
@@ -1,134 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
require 'yaml'
|
||||
|
||||
unless File.exist?('config/vagrant.yml')
|
||||
raise "There is no config/vagrant.yml file.\nCopy config/vagrant.template.yml, make any changes you need, then try again."
|
||||
end
|
||||
|
||||
settings = YAML.load_file 'config/vagrant.yml'
|
||||
|
||||
$script = <<SCRIPT
|
||||
echo Beginning Vagrant provisioning...
|
||||
date > /etc/vagrant_provisioned_at
|
||||
SCRIPT
|
||||
|
||||
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
||||
VAGRANTFILE_API_VERSION = '2'
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
# The most common configuration options are documented and commented below.
|
||||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
config.vm.provision 'shell', inline: $script
|
||||
|
||||
# Every Vagrant development environment requires a box. You can search for
|
||||
# boxes at https://atlas.hashicorp.com/search.
|
||||
config.vm.box = 'ubuntu/trusty64'
|
||||
|
||||
# Share an additional folder to the guest VM. The first argument is
|
||||
# the path on the host to the actual folder. The second argument is
|
||||
# the path on the guest to mount the folder. And the optional third
|
||||
# argument is a set of non-required options.
|
||||
config.vm.synced_folder ".", "/vagrant"
|
||||
|
||||
# Create a forwarded port mapping which allows access to a specific port
|
||||
# within the machine from a port on the host machine. In the example below,
|
||||
# accessing "localhost:5000" will access port 5000 on the guest machine.
|
||||
config.vm.network "forwarded_port", guest: 5000, host: 5000
|
||||
|
||||
# PostgreSQL Server port forwarding
|
||||
config.vm.network "forwarded_port", host: 15432, guest: 5432
|
||||
|
||||
# You can provision with just one of these scripts by user its name, eg:
|
||||
# $ vagrant provision --provision-with postgresql
|
||||
|
||||
config.vm.provision 'build',
|
||||
type: 'shell',
|
||||
path: 'config/vagrant/build_dependency_setup.sh'
|
||||
|
||||
config.vm.provision 'git',
|
||||
type: 'shell',
|
||||
path: 'config/vagrant/git_setup.sh'
|
||||
|
||||
config.vm.provision 'postgresql',
|
||||
type: 'shell',
|
||||
path: 'config/vagrant/postgresql_setup.sh',
|
||||
args: [
|
||||
settings['db']['name'],
|
||||
settings['db']['user'],
|
||||
settings['db']['password'],
|
||||
]
|
||||
|
||||
config.vm.provision 'python',
|
||||
type: 'shell',
|
||||
path: 'config/vagrant/python_setup.sh'
|
||||
|
||||
config.vm.provision 'virtualenv',
|
||||
type: 'shell',
|
||||
path: 'config/vagrant/virtualenv_setup.sh',
|
||||
args: [
|
||||
settings['virtualenv']['envname'],
|
||||
]
|
||||
|
||||
# Will install foreman and, if there's a Procfile, start it:
|
||||
config.vm.provision 'foreman',
|
||||
type: 'shell',
|
||||
path: 'config/vagrant/foreman_setup.sh',
|
||||
run: 'always',
|
||||
args: [
|
||||
settings['virtualenv']['envname'],
|
||||
settings['django']['settings_module'],
|
||||
settings['foreman']['procfile'],
|
||||
]
|
||||
|
||||
# Disable automatic box update checking. If you disable this, then
|
||||
# boxes will only be checked for updates when the user runs
|
||||
# `vagrant box outdated`. This is not recommended.
|
||||
# config.vm.box_check_update = false
|
||||
|
||||
# Create a forwarded port mapping which allows access to a specific port
|
||||
# within the machine from a port on the host machine. In the example below,
|
||||
# accessing "localhost:8080" will access port 80 on the guest machine.
|
||||
# config.vm.network "forwarded_port", guest: 80, host: 8080
|
||||
|
||||
# Create a private network, which allows host-only access to the machine
|
||||
# using a specific IP.
|
||||
# config.vm.network "private_network", ip: "192.168.33.10"
|
||||
|
||||
# Create a public network, which generally matched to bridged network.
|
||||
# Bridged networks make the machine appear as another physical device on
|
||||
# your network.
|
||||
# config.vm.network "public_network"
|
||||
|
||||
# Provider-specific configuration so you can fine-tune various
|
||||
# backing providers for Vagrant. These expose provider-specific options.
|
||||
# Example for VirtualBox:
|
||||
#
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
# # Display the VirtualBox GUI when booting the machine
|
||||
# vb.gui = true
|
||||
#
|
||||
# # Customize the amount of memory on the VM:
|
||||
vb.memory = "1024"
|
||||
end
|
||||
#
|
||||
# View the documentation for the provider you are using for more
|
||||
# information on available options.
|
||||
|
||||
# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
|
||||
# such as FTP and Heroku are also available. See the documentation at
|
||||
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
|
||||
# config.push.define "atlas" do |push|
|
||||
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
|
||||
# end
|
||||
|
||||
# Enable provisioning with a shell script. Additional provisioners such as
|
||||
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
||||
# documentation for more information about their specific syntax and use.
|
||||
# config.vm.provision "shell", inline: <<-SHELL
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y apache2
|
||||
# SHELL
|
||||
end
|
||||
4
app.json
4
app.json
@@ -4,7 +4,7 @@
|
||||
"scripts": {
|
||||
"postdeploy": "python manage.py migrate && python manage.py generateSampleData"
|
||||
},
|
||||
"stack": "heroku-18",
|
||||
"stack": "heroku-20",
|
||||
"env": {
|
||||
"DEBUG": {
|
||||
"required": true
|
||||
@@ -51,4 +51,4 @@
|
||||
"url": "heroku/python"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends request.is_ajax|yesno:'base_ajax.html,base_assets.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% block title %}Audit Asset {{ object.asset_id }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script>
|
||||
|
||||
@@ -209,6 +209,8 @@ class AssetAuditList(AssetList):
|
||||
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
||||
# Don't use the usual success selector - that tries and fails to hit the '10m long cable' helper button...
|
||||
_submit_locator = (By.ID, "id_mark_audited")
|
||||
_close_selector = (By.XPATH, "//button[@data-dismiss='modal']")
|
||||
|
||||
form_items = {
|
||||
'asset_id': (regions.TextBox, (By.ID, 'id_asset_id')),
|
||||
'description': (regions.TextBox, (By.ID, 'id_description')),
|
||||
@@ -240,6 +242,10 @@ class AssetAuditList(AssetList):
|
||||
self.root.find_element(*self._submit_locator).click()
|
||||
# self.wait.until(lambda x: not self.is_displayed) TODO
|
||||
|
||||
def close(self):
|
||||
previous_errors = self.errors
|
||||
self.page.find_element(*self._close_selector).click()
|
||||
|
||||
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\")});")
|
||||
|
||||
@@ -294,31 +294,26 @@ class TestAssetAudit(AutoLoginTest):
|
||||
asset_id = "1111"
|
||||
self.page.set_query(asset_id)
|
||||
self.page.search()
|
||||
mdl = self.page.modal
|
||||
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||
# Do it wrong on purpose to check error display
|
||||
mdl.remove_all_required()
|
||||
mdl.description = ""
|
||||
mdl.submit()
|
||||
# self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||
self.page.modal.remove_all_required()
|
||||
self.page.modal.description = ""
|
||||
self.page.modal.submit()
|
||||
self.wait.until(animation_is_finished())
|
||||
# self.assertTrue(self.driver.find_element_by_id('modal').is_displayed())
|
||||
self.assertIn("This field is required.", mdl.errors["Description"])
|
||||
self.assertIn("This field is required.", self.page.modal.errors["Description"])
|
||||
# Now do it properly
|
||||
new_desc = "A BIG hammer"
|
||||
mdl.description = new_desc
|
||||
mdl.submit()
|
||||
self.page.modal.description = new_desc = "A BIG hammer"
|
||||
self.page.modal.submit()
|
||||
submit_time = timezone.now()
|
||||
self.wait.until(EC.invisibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
|
||||
# Check data is correct
|
||||
audited = models.Asset.objects.get(asset_id="1111")
|
||||
audited = models.Asset.objects.get(asset_id=asset_id)
|
||||
self.assertEqual(audited.description, new_desc)
|
||||
# Make sure audit 'log' was filled out
|
||||
self.assertEqual(self.profile.initials, audited.last_audited_by.initials)
|
||||
self.assertEqual(timezone.now().date(), audited.last_audited_at.date())
|
||||
self.assertEqual(timezone.now().hour, audited.last_audited_at.hour)
|
||||
self.assertEqual(timezone.now().minute, audited.last_audited_at.minute)
|
||||
self.assertEqual(submit_time.replace(microsecond=0), audited.last_audited_at.replace(microsecond=0))
|
||||
# Check we've removed it from the 'needing audit' list
|
||||
self.assertNotIn(asset_id, self.page.assets)
|
||||
|
||||
@@ -326,297 +321,20 @@ class TestAssetAudit(AutoLoginTest):
|
||||
self.assertEqual(len(models.Asset.objects.filter(last_audited_at=None)), len(self.page.assets))
|
||||
|
||||
asset_row = self.page.assets[0]
|
||||
self.driver.find_element(By.XPATH, "//*[@id='asset_table_body']/tr[1]/td[4]/a").click()
|
||||
self.driver.find_element(By.XPATH, "//a[contains(@class,'btn') and contains(., 'Audit')]").click()
|
||||
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertEqual(self.page.modal.asset_id, asset_row.id)
|
||||
|
||||
# First close button is for the not found error
|
||||
self.page.find_element(By.XPATH, '//*[@id="modal"]/div/div/div[1]/button').click()
|
||||
self.page.modal.close()
|
||||
self.wait.until(EC.invisibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
# Make sure audit log was NOT filled out
|
||||
audited = models.Asset.objects.get(asset_id=asset_row.id)
|
||||
self.assertEqual(None, audited.last_audited_by)
|
||||
|
||||
def test_audit_search(self):
|
||||
# Check that a failed search works
|
||||
self.page.set_query("NOTFOUND")
|
||||
self.page.search()
|
||||
self.wait.until(animation_is_finished())
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
self.assertIn("Asset with that ID does not exist!", self.page.error.text)
|
||||
|
||||
|
||||
class TestSupplierValidation(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="SupplierValidationTest", email="SVT@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
cls.supplier = models.Supplier.objects.create(name="Gadgetron Corporation")
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test_create(self):
|
||||
url = reverse('supplier_create')
|
||||
response = self.client.post(url)
|
||||
self.assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
def test_edit(self):
|
||||
url = reverse('supplier_update', kwargs={'pk': self.supplier.pk})
|
||||
response = self.client.post(url, {'name': ""})
|
||||
self.assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
|
||||
class Test404(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="404Test", email="404@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test(self):
|
||||
urls = {'asset_detail', 'asset_update', 'asset_duplicate', 'supplier_detail', 'supplier_update'}
|
||||
for url_name in urls:
|
||||
request_url = reverse(url_name, kwargs={'pk': "0000"})
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
class TestAccessLevels(TestCase):
|
||||
@override_settings(DEBUG=True)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Shortcut to create the levels - bonus side effect of testing the command (hopefully) matches production
|
||||
call_command('generateSampleData')
|
||||
|
||||
# Nothing should be available to the unauthenticated
|
||||
def test_unauthenticated(self):
|
||||
for url in urls.urlpatterns:
|
||||
if url.name is not None:
|
||||
pattern = str(url.pattern)
|
||||
if "json" in url.name or pattern:
|
||||
# TODO
|
||||
pass
|
||||
elif ":pk>" in pattern:
|
||||
request_url = reverse(url.name, kwargs={'pk': 9})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
response = self.client.get(request_url, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
response = self.client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'login')
|
||||
|
||||
def test_basic_access(self):
|
||||
self.assertTrue(self.client.login(username="basic", password="basic"))
|
||||
|
||||
url = reverse('asset_list')
|
||||
response = self.client.get(url)
|
||||
# Check edit and duplicate buttons not shown in list
|
||||
self.assertNotContains(response, 'Edit')
|
||||
self.assertNotContains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = self.client.get(url)
|
||||
self.assertNotContains(response, 'Purchase Details')
|
||||
self.assertNotContains(response, 'View Revision History')
|
||||
|
||||
urls = {'asset_history', 'asset_update', 'asset_duplicate'}
|
||||
for url_name in urls:
|
||||
request_url = reverse(url_name, kwargs={'pk': "9000"})
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
request_url = reverse('supplier_create')
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
request_url = reverse('supplier_update', kwargs={'pk': "1"})
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_keyholder_access(self):
|
||||
self.assertTrue(self.client.login(username="keyholder", password="keyholder"))
|
||||
|
||||
url = reverse('asset_list')
|
||||
response = self.client.get(url)
|
||||
# Check edit and duplicate buttons shown in list
|
||||
self.assertContains(response, 'Edit')
|
||||
self.assertContains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, 'Purchase Details')
|
||||
self.assertContains(response, 'View Revision History')
|
||||
|
||||
# def test_finance_access(self): Level not used in assets currently
|
||||
|
||||
|
||||
class TestFormValidation(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="AssetCreateValidationTest", email="acvt@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
cls.category = models.AssetCategory.objects.create(name="Sound")
|
||||
cls.status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
||||
cls.asset = models.Asset.objects.create(asset_id="9999", description="The Office", status=cls.status, category=cls.category, date_acquired=datetime.date(2018, 6, 15))
|
||||
cls.connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
||||
cls.cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=cls.connector, socket=cls.connector)
|
||||
cls.cable_asset = models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=cls.status, category=cls.category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cls.cable_type, length=10, csa="1.5")
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test_asset_create(self):
|
||||
url = reverse('asset_create')
|
||||
response = self.client.post(url, {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'})
|
||||
self.assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
|
||||
self.assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||
self.assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||
self.assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||
|
||||
def test_cable_create(self):
|
||||
url = reverse('asset_create')
|
||||
response = self.client.post(url, {'asset_id': 'X$%A', 'is_cable': True})
|
||||
self.assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
||||
|
||||
self.assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
||||
def test_asset_edit(self):
|
||||
url = reverse('asset_update', kwargs={'pk': self.asset.asset_id})
|
||||
response = self.client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""})
|
||||
# self.assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
|
||||
self.assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||
self.assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||
self.assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||
|
||||
def test_cable_edit(self):
|
||||
url = reverse('asset_update', kwargs={'pk': self.cable_asset.asset_id})
|
||||
# TODO Why do I have to send is_cable=True here?
|
||||
response = self.client.post(url, {'is_cable': True, 'length': -3, 'csa': -3})
|
||||
|
||||
# TODO Can't figure out how to select the 'none' option...
|
||||
# self.assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
def test_asset_duplicate(self):
|
||||
url = reverse('asset_duplicate', kwargs={'pk': self.cable_asset.asset_id})
|
||||
response = self.client.post(url, {'is_cable': True, 'length': 0, 'csa': 0})
|
||||
|
||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
|
||||
class TestSampleDataGenerator(TestCase):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_generate_sample_data(self):
|
||||
# Run the management command and check there are no exceptions
|
||||
call_command('generateSampleAssetsData')
|
||||
|
||||
# Check there are lots
|
||||
self.assertTrue(models.Asset.objects.all().count() > 50)
|
||||
self.assertTrue(models.Supplier.objects.all().count() > 50)
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_delete_sample_data(self):
|
||||
call_command('deleteSampleData')
|
||||
|
||||
self.assertTrue(models.Asset.objects.all().count() == 0)
|
||||
self.assertTrue(models.Supplier.objects.all().count() == 0)
|
||||
|
||||
def test_production_exception(self):
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleAssetsData')
|
||||
self.assertRaisesRegex(CommandError, ".*production", call_command, 'deleteSampleData')
|
||||
|
||||
|
||||
class TestEmbeddedViews(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="EmbeddedViewsTest", email="embedded@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
|
||||
working = models.AssetStatus.objects.create(name="Working", should_show=True)
|
||||
lighting = models.AssetCategory.objects.create(name="Lighting")
|
||||
|
||||
cls.assets = {
|
||||
1: models.Asset.objects.create(asset_id="1991", description="Spaceflower", status=working, category=lighting, date_acquired=datetime.date(1991, 12, 26))
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
|
||||
def testLoginRedirect(self):
|
||||
request_url = reverse('asset_embed', kwargs={'pk': self.assets[1].asset_id})
|
||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||
|
||||
# Request the page and check it redirects
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
||||
|
||||
# Now login
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
# And check that it no longer redirects
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(len(response.redirect_chain), 0)
|
||||
|
||||
def testLoginCookieWarning(self):
|
||||
login_url = reverse('login_embed')
|
||||
response = self.client.post(login_url, follow=True)
|
||||
self.assertContains(response, "Cookies do not seem to be enabled")
|
||||
|
||||
def testXFrameHeaders(self):
|
||||
asset_url = reverse('asset_embed', kwargs={'pk': self.assets[1].asset_id})
|
||||
login_url = reverse('login_embed')
|
||||
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
response = self.client.get(asset_url, follow=True)
|
||||
with self.assertRaises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
response = self.client.get(login_url, follow=True)
|
||||
with self.assertRaises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
def testOEmbed(self):
|
||||
asset_url = reverse('asset_detail', kwargs={'pk': self.assets[1].asset_id})
|
||||
asset_embed_url = reverse('asset_embed', kwargs={'pk': self.assets[1].asset_id})
|
||||
oembed_url = reverse('asset_oembed', kwargs={'pk': self.assets[1].asset_id})
|
||||
|
||||
alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999})
|
||||
alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999})
|
||||
|
||||
# Test the meta tag is in place
|
||||
response = self.client.get(asset_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertContains(response, '<link rel="alternate" type="application/json+oembed"')
|
||||
self.assertContains(response, oembed_url)
|
||||
|
||||
# Test that the JSON exists
|
||||
response = self.client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, asset_embed_url)
|
||||
|
||||
# Should also work for non-existant
|
||||
response = self.client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, alt_asset_embed_url)
|
||||
296
assets/tests/test_unit.py
Normal file
296
assets/tests/test_unit.py
Normal file
@@ -0,0 +1,296 @@
|
||||
from django.core.management import call_command
|
||||
from assets import models
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from urllib.parse import urlparse
|
||||
from assets import models, urls
|
||||
from reversion import revisions as reversion
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
from django.test import tag
|
||||
import pytest
|
||||
from pytest_django.asserts import assertFormError, assertRedirects
|
||||
|
||||
pytestmark = pytest.mark.django_db # TODO
|
||||
|
||||
|
||||
def response_contains(response, needle):
|
||||
return needle in str(response.content)
|
||||
|
||||
|
||||
def login(client, django_user_model):
|
||||
pwd = 'testuser'
|
||||
usr = "TestUser"
|
||||
profile = django_user_model.objects.create_user(username=usr, email="TestUser@test.com", password=pwd, is_superuser=True, is_active=True, is_staff=True)
|
||||
assert client.login(username=usr, password=pwd)
|
||||
|
||||
|
||||
def create_test_asset():
|
||||
working = models.AssetStatus.objects.create(name="Working", should_show=True)
|
||||
lighting = models.AssetCategory.objects.create(name="Lighting")
|
||||
asset = models.Asset.objects.create(asset_id="1991", description="Spaceflower", status=working, category=lighting, date_acquired=datetime.date(1991, 12, 26))
|
||||
return asset
|
||||
|
||||
|
||||
def create_test_cable():
|
||||
category = models.AssetCategory.objects.create(name="Sound")
|
||||
status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
||||
connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
||||
cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=connector, socket=connector)
|
||||
return models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=status, category=category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cable_type, length=10, csa="1.5")
|
||||
|
||||
|
||||
def test_supplier_create(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
url = reverse('supplier_create')
|
||||
response = client.post(url)
|
||||
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
|
||||
def test_supplier_edit(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
supplier = models.Supplier.objects.create(name="Gadgetron Corporation")
|
||||
url = reverse('supplier_update', kwargs={'pk': supplier.pk})
|
||||
response = client.post(url, {'name': ""})
|
||||
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
|
||||
def test_404(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
urls = {'asset_detail', 'asset_update', 'asset_duplicate', 'supplier_detail', 'supplier_update'}
|
||||
for url_name in urls:
|
||||
request_url = reverse(url_name, kwargs={'pk': "0000"})
|
||||
response = client.get(request_url, follow=True)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_embed_login_redirect(client, django_user_model):
|
||||
request_url = reverse('asset_embed', kwargs={'pk': create_test_asset().asset_id})
|
||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||
|
||||
# Request the page and check it redirects
|
||||
response = client.get(request_url, follow=True)
|
||||
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
||||
|
||||
# Now login
|
||||
login(client, django_user_model)
|
||||
|
||||
# And check that it no longer redirects
|
||||
response = client.get(request_url, follow=True)
|
||||
assert len(response.redirect_chain) == 0
|
||||
|
||||
|
||||
def test_login_cookie_warning(client, django_user_model):
|
||||
login_url = reverse('login_embed')
|
||||
response = client.post(login_url, follow=True)
|
||||
assert "Cookies do not seem to be enabled" in str(response.content)
|
||||
|
||||
|
||||
def test_x_frame_headers(client, django_user_model):
|
||||
asset_url = reverse('asset_embed', kwargs={'pk': create_test_asset().asset_id})
|
||||
login_url = reverse('login_embed')
|
||||
|
||||
login(client, django_user_model)
|
||||
|
||||
response = client.get(asset_url, follow=True)
|
||||
with pytest.raises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
response = client.get(login_url, follow=True)
|
||||
with pytest.raises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
|
||||
def test_oembed(client):
|
||||
asset = create_test_asset()
|
||||
asset_url = reverse('asset_detail', kwargs={'pk': asset.asset_id})
|
||||
asset_embed_url = reverse('asset_embed', kwargs={'pk': asset.asset_id})
|
||||
oembed_url = reverse('asset_oembed', kwargs={'pk': asset.asset_id})
|
||||
|
||||
alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999})
|
||||
alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999})
|
||||
|
||||
# Test the meta tag is in place
|
||||
response = client.get(asset_url, follow=True, HTTP_HOST='example.com')
|
||||
assert '<link rel="alternate" type="application/json+oembed"' in str(response.content)
|
||||
assert oembed_url in str(response.content)
|
||||
|
||||
# Test that the JSON exists
|
||||
response = client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
assert response.status_code == 200
|
||||
assert asset_embed_url in str(response.content)
|
||||
|
||||
# Should also work for non-existant
|
||||
response = client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
assert response.status_code == 200
|
||||
assert alt_asset_embed_url in str(response.content)
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_generate_sample_data(client):
|
||||
# Run the management command and check there are no exceptions
|
||||
call_command('generateSampleAssetsData')
|
||||
|
||||
# Check there are lots
|
||||
assert models.Asset.objects.all().count() > 50
|
||||
assert models.Supplier.objects.all().count() > 50
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_delete_sample_data(client):
|
||||
call_command('deleteSampleData')
|
||||
|
||||
assert models.Asset.objects.all().count() == 0
|
||||
assert models.Supplier.objects.all().count() == 0
|
||||
|
||||
|
||||
def test_production_exception(client):
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
with pytest.raises(CommandError, match=".*production"):
|
||||
call_command('generateSampleAssetsData')
|
||||
call_command('deleteSampleData')
|
||||
|
||||
|
||||
def test_asset_create(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
response = client.post(reverse('asset_create'), {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'})
|
||||
assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
|
||||
assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||
assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||
assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||
|
||||
|
||||
def test_cable_create(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
response = client.post(reverse('asset_create'), {'asset_id': 'X$%A', 'is_cable': True})
|
||||
assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
||||
|
||||
assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
||||
|
||||
|
||||
def test_asset_edit(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
url = reverse('asset_update', kwargs={'pk': create_test_asset().asset_id})
|
||||
response = client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""})
|
||||
# assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||
assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||
assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||
|
||||
|
||||
def test_cable_edit(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
url = reverse('asset_update', kwargs={'pk': create_test_cable().asset_id})
|
||||
# TODO Why do I have to send is_cable=True here?
|
||||
response = client.post(url, {'is_cable': True, 'length': -3, 'csa': -3})
|
||||
|
||||
# TODO Can't figure out how to select the 'none' option...
|
||||
# assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
|
||||
def test_asset_duplicate(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
url = reverse('asset_duplicate', kwargs={'pk': create_test_cable().asset_id})
|
||||
response = client.post(url, {'is_cable': True, 'length': 0, 'csa': 0})
|
||||
|
||||
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def create_asset_one():
|
||||
# Shortcut to create the levels - bonus side effect of testing the command (hopefully) matches production
|
||||
call_command('generateSampleData')
|
||||
# Create an asset with ID 1 to make things easier in loops (we can always use pk=1)
|
||||
category = models.AssetCategory.objects.create(name="Number One")
|
||||
status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True)
|
||||
return models.Asset.objects.create(asset_id="1", description="Half Price Fish", status=status, category=category, date_acquired=datetime.date(2020, 2, 1))
|
||||
|
||||
# Nothing should be available to the unauthenticated
|
||||
|
||||
|
||||
def test_unauthenticated(client):
|
||||
for url in filter(lambda url: url.name is not None and "json" not in str(url), urls.urlpatterns):
|
||||
pattern = str(url.pattern)
|
||||
request_url = ""
|
||||
if ":pk>" in pattern:
|
||||
request_url = reverse(url.name, kwargs={'pk': 1})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
if request_url:
|
||||
response = client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||
# TODO Check the URL here
|
||||
assert response_contains(response, 'Login')
|
||||
|
||||
|
||||
def test_basic_access(client):
|
||||
create_asset_one()
|
||||
client.login(username="basic", password="basic")
|
||||
|
||||
url = reverse('asset_list')
|
||||
response = client.get(url)
|
||||
# Check edit and duplicate buttons NOT shown in list
|
||||
assert not response_contains(response, 'Edit')
|
||||
assert not response_contains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = client.get(url)
|
||||
assert not response_contains(response, 'Purchase Details')
|
||||
assert not response_contains(response, 'View Revision History')
|
||||
|
||||
urls = {'asset_history', 'asset_update', 'asset_duplicate'}
|
||||
for url_name in urls:
|
||||
request_url = reverse(url_name, kwargs={'pk': "9000"})
|
||||
response = client.get(request_url, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
request_url = reverse('supplier_create')
|
||||
response = client.get(request_url, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
request_url = reverse('supplier_update', kwargs={'pk': "1"})
|
||||
response = client.get(request_url, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_keyholder_access(client):
|
||||
create_asset_one()
|
||||
client.login(username="keyholder", password="keyholder")
|
||||
|
||||
url = reverse('asset_list')
|
||||
response = client.get(url)
|
||||
# Check edit and duplicate buttons shown in list
|
||||
assert response_contains(response, 'Edit')
|
||||
assert response_contains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = client.get(url)
|
||||
assert response_contains(response, 'Purchase Details')
|
||||
assert response_contains(response, 'View Revision History')
|
||||
|
||||
|
||||
def test_page_titles(admin_client):
|
||||
for url in filter(lambda url: url.name is not None and not any(s in url.name for s in ["json", "embed"]), urls.urlpatterns):
|
||||
request_url = ""
|
||||
if ":pk>" in str(url.pattern):
|
||||
request_url = reverse(url.name, kwargs={'pk': "1"})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
response = admin_client.get(request_url)
|
||||
if hasattr(response, "context_data") and "page_title" in response.context_data:
|
||||
expected_title = response.context_data["page_title"]
|
||||
assert response_contains(response, '<title>{} | Rig Information Gathering System</title>'.format(expected_title))
|
||||
@@ -14,7 +14,7 @@ from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from versioning import versioning
|
||||
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin
|
||||
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, is_ajax
|
||||
from itertools import chain
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView):
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('asset_update', kwargs={'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
|
||||
@@ -211,6 +211,11 @@ class AssetAudit(AssetEdit):
|
||||
template_name = 'asset_audit.html'
|
||||
form_class = forms.AssetAuditForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["page_title"] = "Audit Asset: {}".format(self.object.display_id)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
# TODO For some reason this doesn't stick when done in form_valid??
|
||||
asset = self.get_object()
|
||||
@@ -230,7 +235,7 @@ class SupplierList(GenericListView):
|
||||
context['edit'] = 'supplier_update'
|
||||
context['can_edit'] = self.request.user.has_perm('assets.change_supplier')
|
||||
context['detail'] = 'supplier_detail'
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
@@ -258,7 +263,7 @@ class SupplierDetail(GenericDetailView):
|
||||
context['detail_link'] = 'supplier_detail'
|
||||
context['associated'] = 'partials/associated_assets.html'
|
||||
context['associated2'] = ''
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
@@ -272,7 +277,7 @@ class SupplierCreate(GenericCreateView, ModalURLMixin):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SupplierCreate, self).get_context_data(**kwargs)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
@@ -288,7 +293,7 @@ class SupplierUpdate(GenericUpdateView, ModalURLMixin):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SupplierUpdate, self).get_context_data(**kwargs)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
django:
|
||||
settings_module: PyRIGS.settings
|
||||
virtualenv:
|
||||
envname: myvirtualenv
|
||||
foreman:
|
||||
procfile: Procfile
|
||||
db:
|
||||
name: postgres_db
|
||||
user: postgres_db
|
||||
password: postgres_db
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Via https://github.com/kiere/vagrant-heroku-cedar-14/blob/master/config/vagrant/build_dependency_setup.sh
|
||||
|
||||
echo "=== Begin Vagrant Provisioning using 'config/vagrant/build_dependency_setup.sh'"
|
||||
|
||||
# Install build dependencies for a sane build environment
|
||||
apt-get -y update
|
||||
apt-get -y install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev
|
||||
|
||||
# Other things that we may need installed before anything else.
|
||||
apt-get install -y libmemcached-dev
|
||||
apt-get build-dep python-lxml
|
||||
|
||||
echo "=== End Vagrant Provisioning using 'config/vagrant/build_dependency_setup.sh'"
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install and (if there's a Procfile) start foreman.
|
||||
# Needs to come after the virtualenv has been set up.
|
||||
|
||||
# Expects three arguments:
|
||||
VIRTUALENV_NAME=$1
|
||||
DJANGO_SETTINGS_MODULE=$2
|
||||
PROCFILE=$3
|
||||
|
||||
echo "=== Begin Vagrant Provisioning using 'config/vagrant/foreman_setup.sh'"
|
||||
|
||||
gem install foreman --no-ri --no-rdoc
|
||||
|
||||
if ! grep -Fq "DJANGO_SETTINGS_MODULE" /home/vagrant/.bashrc; then
|
||||
echo "export DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE}" >> /home/vagrant/.bashrc
|
||||
fi
|
||||
|
||||
|
||||
if [[ -f /vagrant/$PROCFILE ]]; then
|
||||
echo "Procfile found; starting foreman."
|
||||
|
||||
export DJANGO_SETTINGS_MODULE="$DJANGO_SETTINGS_MODULE"
|
||||
|
||||
# Ensure the virtualenv settings in .profile are loaded:
|
||||
source /home/vagrant/.profile
|
||||
|
||||
# Run with & to release the terminal.
|
||||
# Although that may also rely on the Procfile's processes having their
|
||||
# output sent to a file, not stdout/stderr.
|
||||
foreman start -f /vagrant/$PROCFILE &
|
||||
else
|
||||
echo "No Procfile found; not starting foreman."
|
||||
fi
|
||||
|
||||
echo "=== End Vagrant Provisioning using 'config/vagrant/foreman_setup.sh'"
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Via https://github.com/kiere/vagrant-heroku-cedar-14/blob/master/config/vagrant/git_setup.sh
|
||||
|
||||
echo "=== Begin Vagrant Provisioning using 'config/vagrant/git_setup.sh'"
|
||||
|
||||
# Install Git if not available
|
||||
if [ -z `which git` ]; then
|
||||
echo "===== Installing Git"
|
||||
apt-get -y update
|
||||
apt-get -y install git-core
|
||||
fi
|
||||
|
||||
echo "=== End Vagrant Provisioning using 'config/vagrant/git_setup.sh'"
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
# Expects three arguments:
|
||||
# $1 - database name
|
||||
# $2 - database user
|
||||
# #3 - database user password
|
||||
|
||||
# Via https://github.com/kiere/vagrant-heroku-cedar-14/blob/master/config/vagrant/postgresql_setup.sh
|
||||
|
||||
echo "=== Begin Vagrant Provisioning using 'config/vagrant/postgresql_setup.sh'"
|
||||
|
||||
APP_DB_NAME=$1
|
||||
APP_DB_USER=$2
|
||||
APP_DB_PASS=$3
|
||||
|
||||
# Edit the following to change the version of PostgreSQL that is installed
|
||||
PG_VERSION=9.4
|
||||
|
||||
###########################################################
|
||||
# Changes below this line are probably not necessary
|
||||
###########################################################
|
||||
print_db_usage () {
|
||||
echo "Your PostgreSQL database has been setup and can be accessed on your local machine on the forwarded port (default: 15432)"
|
||||
echo " Host: localhost"
|
||||
echo " Port: 15432"
|
||||
echo " Database: $APP_DB_NAME"
|
||||
echo " Username: $APP_DB_USER"
|
||||
echo " Password: $APP_DB_PASS"
|
||||
echo ""
|
||||
echo "Admin access to postgres user via VM:"
|
||||
echo " vagrant ssh"
|
||||
echo " sudo su - postgres"
|
||||
echo ""
|
||||
echo "psql access to app database user via VM:"
|
||||
echo " vagrant ssh"
|
||||
echo " sudo su - postgres"
|
||||
echo " PGUSER=$APP_DB_USER PGPASSWORD=$APP_DB_PASS psql -h localhost $APP_DB_NAME"
|
||||
echo ""
|
||||
echo "Env variable for application development:"
|
||||
echo " DATABASE_URL=postgresql://$APP_DB_USER:$APP_DB_PASS@localhost:15432/$APP_DB_NAME"
|
||||
echo ""
|
||||
echo "Local command to access the database via psql:"
|
||||
echo " PGUSER=$APP_DB_USER PGPASSWORD=$APP_DB_PASS psql -h localhost -p 15432 $APP_DB_NAME"
|
||||
}
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
PROVISIONED_ON=/etc/vm_provision_on_timestamp
|
||||
if [ -f "$PROVISIONED_ON" ]
|
||||
then
|
||||
echo "VM was already provisioned at: $(cat $PROVISIONED_ON)"
|
||||
echo "To run system updates manually login via 'vagrant ssh' and run 'apt-get update && apt-get upgrade'"
|
||||
echo ""
|
||||
print_db_usage
|
||||
exit
|
||||
fi
|
||||
|
||||
PG_REPO_APT_SOURCE=/etc/apt/sources.list.d/pgdg.list
|
||||
if [ ! -f "$PG_REPO_APT_SOURCE" ]
|
||||
then
|
||||
# Add PG apt repo:
|
||||
echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > "$PG_REPO_APT_SOURCE"
|
||||
|
||||
# Add PGDG repo key:
|
||||
wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | apt-key add -
|
||||
fi
|
||||
|
||||
# Update package list and upgrade all packages
|
||||
apt-get update
|
||||
apt-get -y upgrade
|
||||
|
||||
apt-get -y install "postgresql-$PG_VERSION" "postgresql-contrib-$PG_VERSION"
|
||||
apt-get -y install libpq-dev # For building ruby 'pg' gem
|
||||
|
||||
PG_CONF="/etc/postgresql/$PG_VERSION/main/postgresql.conf"
|
||||
PG_HBA="/etc/postgresql/$PG_VERSION/main/pg_hba.conf"
|
||||
PG_DIR="/var/lib/postgresql/$PG_VERSION/main"
|
||||
|
||||
# Edit postgresql.conf to change listen address to '*':
|
||||
sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" "$PG_CONF"
|
||||
|
||||
# Append to pg_hba.conf to add password auth:
|
||||
echo "host all all all md5" >> "$PG_HBA"
|
||||
|
||||
# Explicitly set default client_encoding
|
||||
echo "client_encoding = utf8" >> "$PG_CONF"
|
||||
|
||||
# Restart so that all new config is loaded:
|
||||
service postgresql restart
|
||||
|
||||
cat << EOF | su - postgres -c psql
|
||||
-- Create the database user:
|
||||
CREATE USER $APP_DB_USER PASSWORD '$APP_DB_PASS';
|
||||
EOF
|
||||
|
||||
cat << EOF | su - postgres -c psql
|
||||
-- Create the database:
|
||||
CREATE DATABASE $APP_DB_NAME WITH OWNER=$APP_DB_USER
|
||||
LC_COLLATE='en_US.utf8'
|
||||
LC_CTYPE='en_US.utf8'
|
||||
ENCODING='UTF8'
|
||||
TEMPLATE=template0;
|
||||
EOF
|
||||
|
||||
# Tag the provision time:
|
||||
date > "$PROVISIONED_ON"
|
||||
|
||||
echo "Successfully created PostgreSQL dev virtual machine."
|
||||
echo ""
|
||||
print_db_usage
|
||||
|
||||
echo "=== End Vagrant Provisioning using 'config/vagrant/postgresql_setup.sh'"
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install python and required python modules.
|
||||
# pip and virtualenv are in virtualenv_setup.sh
|
||||
|
||||
# Initial part of this via
|
||||
# https://github.com/torchbox/vagrant-django-base/blob/master/install.sh
|
||||
|
||||
echo "=== Begin Vagrant Provisioning using 'config/vagrant/python_setup.sh'"
|
||||
|
||||
apt-get update -y
|
||||
|
||||
# Python dev packages
|
||||
apt-get install -y python python-dev python-setuptools python-pip
|
||||
|
||||
# Dependencies for image processing with Pillow (drop-in replacement for PIL)
|
||||
# supporting: jpeg, tiff, png, freetype, littlecms
|
||||
apt-get install -y libjpeg-dev libtiff-dev zlib1g-dev libfreetype6-dev liblcms2-dev
|
||||
|
||||
# lxml dependencies
|
||||
apt-get install -y libxml2-dev libxslt1-dev python-dev
|
||||
|
||||
echo "=== End Vagrant Provisioning using 'config/vagrant/python_setup.sh'"
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This will:
|
||||
# * Install pip, virtualenv and virtualenvwrapper.
|
||||
# * Set up virtualenvwrapper's paths etc.
|
||||
# * Create a new virtualenv.
|
||||
# * Install any pip requirements from the requirements.txt file.
|
||||
|
||||
# Expects one argument, the name of the virtualenv.
|
||||
|
||||
# The name we'll use for the virtualenv in which we'll install requirements:
|
||||
VENV_NAME=$1
|
||||
|
||||
echo "=== Begin Vagrant Provisioning using 'config/vagrant/virtualenv_setup.sh'"
|
||||
|
||||
# virtualenv global setup
|
||||
if ! command -v pip; then
|
||||
easy_install -U pip
|
||||
fi
|
||||
|
||||
if [[ ! -f /usr/local/bin/virtualenv ]]; then
|
||||
easy_install virtualenv virtualenvwrapper
|
||||
fi
|
||||
|
||||
|
||||
# If it doesn't look like .bashrc has the required virtualenvwrapper lines in,
|
||||
# then add them.
|
||||
if ! grep -Fq "WORKON_HOME" /home/vagrant/.profile; then
|
||||
echo "Adding virtualenvwrapper locations to .profile"
|
||||
|
||||
if [[ -d /vagrant/config/virtualenvwrapper/vagrant ]]; then
|
||||
echo "export VIRTUALENVWRAPPER_HOOK_DIR=/vagrant/config/virtualenvwrapper/vagrant" >> /home/vagrant/.profile
|
||||
fi
|
||||
|
||||
echo "export WORKON_HOME=/home/vagrant/.virtualenvs" >> /home/vagrant/.profile
|
||||
echo "export PROJECT_HOME=/home/vagrant/Devel" >> /home/vagrant/.profile
|
||||
echo "source /usr/local/bin/virtualenvwrapper.sh" >> /home/vagrant/.profile
|
||||
fi
|
||||
|
||||
# Get .virtualenvwrapper env variables set up:
|
||||
source /home/vagrant/.profile
|
||||
|
||||
if [[ -d /home/vagrant/.virtualenvs/$VENV_NAME ]]; then
|
||||
echo "Activating virtualenv $VENV_NAME."
|
||||
workon $VENV_NAME
|
||||
else
|
||||
echo "Making new virtualenv $VENV_NAME."
|
||||
# Also switches to the virtualenv:
|
||||
mkvirtualenv $VENV_NAME
|
||||
|
||||
# So that we can install things with pip while ssh'd in as vagrant user:
|
||||
sudo chown -R vagrant:vagrant /home/vagrant/.virtualenvs/$VENV_NAME/
|
||||
|
||||
# Automatically switch to the virtual env on log in:
|
||||
echo "workon $VENV_NAME" >> /home/vagrant/.profile
|
||||
fi
|
||||
|
||||
|
||||
# If we have a requirements.txt file in this project, then install
|
||||
# everything in it with pip in a new virtualenv.
|
||||
if [[ -f /vagrant/requirements.txt ]]; then
|
||||
echo "Installing from ./requirements.txt with pip."
|
||||
pip install -r /vagrant/requirements.txt
|
||||
fi
|
||||
|
||||
echo "=== End Vagrant Provisioning using 'config/vagrant/virtualenv_setup.sh'"
|
||||
9827
package-lock.json
generated
9827
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"repository": "https://github.com/nottinghamtec/PyRIGS",
|
||||
"author": "Tom Price",
|
||||
"license": "MIT",
|
||||
"license": "Custom",
|
||||
"dependencies": {
|
||||
"@forevolve/bootstrap-dark": "^1.0.0-alpha.1075",
|
||||
"@fortawesome/fontawesome-free": "^5.13.1",
|
||||
@@ -16,8 +16,6 @@
|
||||
"dark-mode-switch": "^1.0.0",
|
||||
"flatpickr": "^4.6.6",
|
||||
"fullcalendar": "^5.3.2",
|
||||
"gulp-if": "^3.0.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"jquery": "^3.5.1",
|
||||
"jquery-ui-dist": "^1.12.1",
|
||||
"konami": "^1.6.2",
|
||||
@@ -30,7 +28,9 @@
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-flatten": "^0.4.0",
|
||||
"gulp-if": "^3.0.0",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sass": "^4.1.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-terser": "^1.2.0",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = myproject.settings
|
||||
DJANGO_SETTINGS_MODULE = PyRIGS.settings
|
||||
# FAIL_INVALID_TEMPLATE_VARS = True
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
ansicolors==1.1.8
|
||||
asgiref==3.2.10
|
||||
asgiref==3.3.1
|
||||
backports.tempfile==1.0
|
||||
backports.weakref==1.0.post1
|
||||
beautifulsoup4==4.9.3
|
||||
cachetools==4.1.1
|
||||
certifi==2020.6.20
|
||||
chardet==3.0.4
|
||||
cachetools==4.2.1
|
||||
certifi==2020.12.5
|
||||
chardet==4.0.0
|
||||
configparser==5.0.1
|
||||
contextlib2==0.6.0.post1
|
||||
cssselect==1.1.0
|
||||
@@ -13,8 +13,8 @@ cssutils==1.0.2
|
||||
diff-match-patch==20200713
|
||||
dj-database-url==0.5.0
|
||||
dj-static==0.0.6
|
||||
Django==3.1.2
|
||||
django-debug-toolbar==3.1.1
|
||||
Django==3.1.5
|
||||
django-debug-toolbar==3.2
|
||||
django-filter==2.4.0
|
||||
django-gulp==4.1.0
|
||||
django-ical==1.7.1
|
||||
@@ -22,59 +22,59 @@ django-livereload==1.7
|
||||
django-livereload-server==0.3.2
|
||||
django-recaptcha==2.0.6
|
||||
django-recurrence==1.10.3
|
||||
django-registration-redux==2.8
|
||||
django-reversion==3.0.8
|
||||
django-registration-redux==2.9
|
||||
django-reversion==3.0.9
|
||||
django-toolbelt==0.0.1
|
||||
django-widget-tweaks==1.4.8
|
||||
envparse==0.2.0
|
||||
env-tools==2.2.0
|
||||
gunicorn==20.0.4
|
||||
icalendar==4.0.7
|
||||
idna==2.10
|
||||
imgurpython==1.1.7
|
||||
importlib-metadata==2.0.0
|
||||
importlib-metadata==3.4.0
|
||||
lxml==4.6.2
|
||||
Markdown==3.3
|
||||
msgpack==1.0.0
|
||||
packaging==20.4
|
||||
pep517==0.8.2
|
||||
Pillow==7.2.0
|
||||
Markdown==3.3.3
|
||||
msgpack==1.0.2
|
||||
packaging==20.8
|
||||
pep517==0.9.1
|
||||
Pillow==8.1.0
|
||||
pluggy==0.13.1
|
||||
premailer==3.7.0
|
||||
progress==1.5
|
||||
psutil==5.7.2
|
||||
psutil==5.8.0
|
||||
psycopg2==2.8.6
|
||||
psycopg2-binary==2.8.6
|
||||
Pygments==2.7.1
|
||||
Pygments==2.7.4
|
||||
pyparsing==2.4.7
|
||||
PyPDF2==1.26.0
|
||||
PyPOM==2.2.0
|
||||
python-dateutil==2.8.1
|
||||
pytoml==0.1.21
|
||||
pytz==2020.1
|
||||
pytz==2020.5
|
||||
pytest-django==4.1.0
|
||||
pytest-xdist==2.2.0
|
||||
pytest-cov==2.11.1
|
||||
raven==6.10.0
|
||||
reportlab==3.5.53
|
||||
requests==2.24.0
|
||||
reportlab==3.5.60
|
||||
requests==2.25.1
|
||||
retrying==1.3.3
|
||||
selenium==3.141.0
|
||||
simplejson==3.17.2
|
||||
six==1.15.0
|
||||
soupsieve==2.0.1
|
||||
soupsieve==2.1
|
||||
sqlparse==0.4.1
|
||||
static3==0.7.0
|
||||
svg2rlg==0.3
|
||||
tini==3.0.1
|
||||
tornado==6.0.4
|
||||
urllib3==1.25.10
|
||||
tornado==6.1
|
||||
urllib3==1.26.2
|
||||
whitenoise==5.2.0
|
||||
yolk==0.4.3
|
||||
z3c.rml==3.10.0
|
||||
zipp==3.3.0
|
||||
z3c.rml==4.1.2
|
||||
zipp==3.4.0
|
||||
zope.component==4.6.2
|
||||
zope.deferredimport==4.3.1
|
||||
zope.deprecation==4.4.0
|
||||
zope.event==4.5.0
|
||||
zope.hookable==5.0.1
|
||||
zope.interface==5.1.2
|
||||
zope.interface==5.2.0
|
||||
zope.proxy==4.3.5
|
||||
zope.schema==6.0.0
|
||||
zope.schema==6.0.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
python-3.8.2
|
||||
python-3.9.1
|
||||
|
||||
@@ -305,6 +305,3 @@ class TestVersioningViews(TestCase):
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertContains(response, "Test Person")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
# Functional Tests
|
||||
|
||||
Reference in New Issue
Block a user