diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..25975d3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,10 @@ +before_script: + - apt-get -qq update + - apt-get -qq install -y python3 python3-venv python3-pip + - python3 -m venv venv + - source venv/bin/activate + - pip install -r requirements.txt pylint + +pylint: + script: + - pylint marietje/marietje marietje/metrics marietje/playerapi marietje/queues marietje/songs marietje/stats diff --git a/marietje/api/urls.py b/marietje/api/urls.py index ac0ad39..8fe583d 100644 --- a/marietje/api/urls.py +++ b/marietje/api/urls.py @@ -13,7 +13,6 @@ urlpatterns = [ url(r'^request', views.request), url(r'^report', views.report), url(r'^skip', views.skip), - url(r'^moveup', views.move_up), url(r'^movedown', views.move_down), url(r'^cancel', views.cancel), url(r'^upload', views.upload), diff --git a/marietje/api/views.py b/marietje/api/views.py index 30e9871..5dc453e 100644 --- a/marietje/api/views.py +++ b/marietje/api/views.py @@ -111,7 +111,7 @@ def songs(request): except EmptyPage: songs = paginator.page(paginator.num_pages) - songs_dict = [song_to_dict(song, user=True) for song in songs.object_list] + songs_dict = [song_to_dict(song, include_user=True) for song in songs.object_list] return JsonResponse({ 'per_page': pagesize, 'current_page': page, @@ -188,16 +188,6 @@ def skip(request): return JsonResponse({}) -@require_http_methods(["POST"]) -@api_auth_required -def move_up(request): - if not request.user.has_perm('queues.can_move'): - return HttpResponseForbidden() - playlist_song = get_object_or_404(PlaylistSong, id=request.POST.get('id')) - playlist_song.move_up() - return JsonResponse({}) - - @require_http_methods(["POST"]) @api_auth_required def move_down(request): diff --git a/marietje/marietje/forms.py b/marietje/marietje/forms.py index c04b1c1..41e741a 100644 --- a/marietje/marietje/forms.py +++ b/marietje/marietje/forms.py @@ -5,9 +5,6 @@ from django.utils.translation import ugettext_lazy as _ class AuthenticationForm(BaseAuthenticationForm): - def __init__(self, request=None, *args, **kwargs): - super(AuthenticationForm, self).__init__(request, *args, **kwargs) - def confirm_login_allowed(self, user): if user.activation_token: raise forms.ValidationError( diff --git a/marietje/marietje/models.py b/marietje/marietje/models.py index b89c502..6421c5d 100644 --- a/marietje/marietje/models.py +++ b/marietje/marietje/models.py @@ -1,14 +1,14 @@ -from django.db import models -from queues.models import Queue from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager from django.contrib.auth.hashers import make_password from django.contrib.auth.models import PermissionsMixin from django.contrib.auth.validators import ASCIIUsernameValidator, UnicodeUsernameValidator +from django.core.mail import send_mail +from django.db import models from django.utils import six, timezone from django.utils.translation import ugettext_lazy as _ -from django.core.mail import send_mail from marietje.utils import get_first_queue +from queues.models import Queue class UserManager(BaseUserManager): diff --git a/marietje/marietje/settings.py b/marietje/marietje/settings.py index 5d83da5..3109ead 100644 --- a/marietje/marietje/settings.py +++ b/marietje/marietje/settings.py @@ -62,7 +62,7 @@ DATABASES = { 'PASSWORD': 'v8TzZwdAdSi7Tk5I', 'HOST': 'localhost', 'PORT': '3306', - 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" }, + 'OPTIONS': {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}, } } @@ -95,12 +95,12 @@ CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/MarietjeDjango_cache/default', - 'OPTIONS': { 'MAX_ENTRIES': 1000 }, + 'OPTIONS': {'MAX_ENTRIES': 1000}, }, 'song_search': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/MarietjeDjango_cache/song_search', - 'OPTIONS': { 'MAX_ENTRIES': 1000 }, + 'OPTIONS': {'MAX_ENTRIES': 1000}, }, 'userstats': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', @@ -144,8 +144,8 @@ CONTACT_EMAIL = 'marietje@science.ru.nl' STATS_TOP_COUNT = 50 STATS_REQUEST_IGNORE_USER_IDS = [] -ISSUES_URL = 'https://gitlab.science.ru.nl/Marietje/MarietjeDjango/issues' -MERGE_REQUESTS_URL = 'https://gitlab.science.ru.nl/Marietje/MarietjeDjango/merge_requests' +ISSUES_URL = 'https://gitlab.science.ru.nl/dsprenkels/MarietjeDjango/issues' +MERGE_REQUESTS_URL = 'https://gitlab.science.ru.nl/dsprenkels/MarietjeDjango/merge_requests' TRUSTED_IP_RANGES = [ '131.174.0.0/16', # RU wired '145.116.136.0/22', # eduroam diff --git a/marietje/marietje/static/css/custom.css b/marietje/marietje/static/css/custom.css index f97451c..c9e8d79 100644 --- a/marietje/marietje/static/css/custom.css +++ b/marietje/marietje/static/css/custom.css @@ -21,3 +21,11 @@ footer { text-align: center; } + +.marietjequeue { + color: #777777; +} + +.marietjequeue-start { + border-top: 4px double #777777; +} \ No newline at end of file diff --git a/marietje/marietje/static/js/queue.js b/marietje/marietje/static/js/queue.js index 1d6e209..cfcdcb2 100644 --- a/marietje/marietje/static/js/queue.js +++ b/marietje/marietje/static/js/queue.js @@ -49,13 +49,16 @@ $(function () { var songId = $(this).data('report-song-id'); var message = prompt("What is wrong with the song?"); if (message == "") { - alert("Please enter a message."); + createAlert('danger', 'Please enter a message.'); return false } + if (message == null) { + return false + } $.post('/api/report', {id: songId, msg: message, csrfmiddlewaretoken: csrf_token}, function (result) { console.log(result); if (result.success) { - alert("Thanks for the report!"); + createAlert('success', 'Thanks for your song report!'); } }); return false; @@ -194,27 +197,56 @@ function renderQueue(playNextAt, now) { $('.queuebody').empty(); var timeToPlay = playNextAt - now; + var canDeletePrevious = false; $.each(queue, function (id, song) { var requestedBy = song.requested_by; + var reqMarietje = requestedBy != 'Marietje'; + var startMarietje = false + + //checks if id is the last item and returns false if the next song is Marietje, while the current song is not. + if(id === queue.length-1){ + var requestNext = false + } else { + var requestNext = !((queue[id+1].requested_by === 'Marietje') && (requestedBy !== 'Marietje')) + } + //checks if id is the first item and returns false if the previous song is not Marietje, while the current song is. + if(id === 0){ + var requestPrev = false + } else { + var prevItem = queue[id-1].id + if(queue[id-1].requested_by !== 'Marietje'){ + var requestPrev = false + if (requestedBy == 'Marietje'){ + var startMarietje = true + } else { + var requestPrev = true + } + } else {var requestPrev = true} + } + var canDelete = song.can_move_down || canMoveSongs; - var canMoveDown = canDelete && id < queue.length - 1; + var canMoveUp = canMoveSongs && requestPrev || canDeletePrevious && reqMarietje && requestPrev; + var canMoveDown = canMoveSongs && requestNext || canDelete && reqMarietje && requestNext; var artist = song.song.artist.trim() === '' ? '?' : song.song.artist; var title = song.song.title.trim() === '' ? '?' : song.song.title; + var marietjeclass = reqMarietje ? '' : ' class="marietjequeue"'; + var marietjestartclass = startMarietje ? ' class="marietjequeue marietjequeue-start"' : ''; showTime = showTimeToPlay ? (timeToPlay < 0 ? '' : timeToPlay.secondsToMMSS()) : (playNextAt < now ? '' : playNextAt.timestampToHHMMSS()) - $('.queuebody:last-child').append('' + artist + $('.queuebody:last-child').append('' + '' + artist + '' + title + '' + requestedBy + '' + showTime + '' + '        '); timeToPlay += parseInt(song.song.duration); + canDeletePrevious = canDelete if(playNextAt >= now) { playNextAt += parseInt(song.song.duration); diff --git a/marietje/marietje/utils.py b/marietje/marietje/utils.py index 8c6f03b..ae32c35 100644 --- a/marietje/marietje/utils.py +++ b/marietje/marietje/utils.py @@ -1,10 +1,13 @@ -import socket, struct, binascii +import binascii +import socket +import struct + from django.conf import settings -from queues.models import Queue, Playlist from django.http import StreamingHttpResponse +from queues.models import Queue, Playlist -def song_to_dict(song, hash=False, user=False, replaygain=False): +def song_to_dict(song, include_hash=False, include_user=False, include_replaygain=False, **options): data = { 'id': song.id, 'artist': song.artist, @@ -12,13 +15,13 @@ def song_to_dict(song, hash=False, user=False, replaygain=False): 'duration': song.duration, } - if hash: + if include_hash: data['hash'] = song.hash - if user is not None and song.user is not None and song.user.name: + if include_user is not None and song.user is not None and song.user.name: data['uploader_name'] = song.user.name - if replaygain: + if include_replaygain: data['rg_gain'] = song.rg_gain data['rg_peak'] = song.rg_peak @@ -44,9 +47,9 @@ def send_to_bertha(file): for chunk in file.chunks(): sock.sendall(chunk) sock.shutdown(socket.SHUT_WR) - hash = binascii.hexlify(sock.recv(64)) + song_hash = binascii.hexlify(sock.recv(64)) sock.close() - return hash + return song_hash def get_first_queue(): @@ -61,10 +64,10 @@ def get_first_queue(): return queue -def bertha_stream(hash): +def bertha_stream(song_hash): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(settings.BERTHA_HOST) - sock.sendall(struct.pack("?$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata, + blah, + bla + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=50 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + x, y, z, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception". +overgeneral-exceptions=Exception