diff --git a/README.md b/README.md index d802cba05..80b3cfb75 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ mygpo is licensed under the GNU Affero General Public License Version 3. See fil Installation ------------ -See the [installation instructions](http://gpoddernet.readthedocs.io/en/latest/dev/installation.html) for details. +See the [installation instructions](https://gpoddernet.readthedocs.io/en/latest/dev/installation.html) for details. Bugs @@ -21,17 +21,22 @@ Please report bugs in the [GitHub issue tracker](https://github.com/gpodder/mygp Contributing ------------ -gpodder.net is an open source project and your contributions are wanted and appreciated. To get started please see the [developer documentation](http://gpoddernet.readthedocs.io/en/latest/dev/index.html). +gpodder.net is an open source project and your contributions are wanted and appreciated. To get started please see the [developer documentation](https://gpoddernet.readthedocs.io/en/latest/dev/index.html). +Slack +------------ +Join our Slack channel: [gpodder-net.slack.com](https://gpodder-net.slack.com/) + +[Invitation link](https://join.slack.com/t/gpodder-net/shared_invite/zt-aaiagl5i-uZeqVR8w1Yf_G~9rhktRfw) Mailing List ------------ -mygpo and gpodder.net related issues are discussed on the [gPodder Mailing List](https://gpodder.github.io/docs/mailing-list.html). +gpodder.org related issues are discussed on the [gPodder Mailing List](https://gpodder.github.io/docs/mailing-list.html). Documentation ------------- -Documentation, especially for the API, is stored in the [**doc** folder](https://github.com/gpodder/mygpo/tree/master/doc) and can be read on [ReadTheDocs](http://gpoddernet.readthedocs.io/en/latest/index.html). +Documentation, especially for the API, is stored in the [**doc** folder](https://github.com/gpodder/mygpo/tree/master/doc) and can be read on [ReadTheDocs](https://gpoddernet.readthedocs.io/en/latest/index.html). Name (Why mygpo?) diff --git a/conf/gunicorn.conf.py b/conf/gunicorn.conf.py index e955d4526..c72ebeebc 100644 --- a/conf/gunicorn.conf.py +++ b/conf/gunicorn.conf.py @@ -1,4 +1,5 @@ import multiprocessing +import os bind = "unix:/tmp/mygpo.sock" workers = multiprocessing.cpu_count() @@ -6,16 +7,16 @@ # The maximum number of requests a worker will process before restarting. # max_requests = 1000 -errorlog='/var/log/gunicorn/error.log' -accesslog='/var/log/gunicorn/access.log' -loglevel='info' +errorlog = "/var/log/gunicorn/error.log" +accesslog = "/var/log/gunicorn/access.log" +loglevel = "info" timeout = 120 graceful_timeout = 60 def get_bool(name, default): - return os.getenv(name, str(default)).lower() == 'true' + return os.getenv(name, str(default)).lower() == "true" def _post_fork_handler(server, worker): @@ -24,7 +25,7 @@ def _post_fork_handler(server, worker): # check if we want to use gevent -_USE_GEVENT = get_bool('USE_GEVENT', False) +_USE_GEVENT = get_bool("USE_GEVENT", False) try: @@ -38,7 +39,7 @@ def _post_fork_handler(server, worker): # Active gevent-related settings worker_connections = 100 - worker_class = 'gevent' + worker_class = "gevent" # activate the handler post_fork = _post_fork_handler diff --git a/conftest.py b/conftest.py index 61629a16d..ff4024853 100644 --- a/conftest.py +++ b/conftest.py @@ -3,7 +3,7 @@ @pytest.fixture(autouse=True) def enable_db_access_for_all_tests(db): - """ Enable DB access for all tests + """Enable DB access for all tests - http://pytest-django.readthedocs.io/en/latest/faq.html#how-can-i-give-database-access-to-all-my-tests-without-the-django-db-marker """ + http://pytest-django.readthedocs.io/en/latest/faq.html#how-can-i-give-database-access-to-all-my-tests-without-the-django-db-marker""" pass diff --git a/doc/_ext/jsonlexer.py b/doc/_ext/jsonlexer.py index 1a76bd510..121a99c5d 100644 --- a/doc/_ext/jsonlexer.py +++ b/doc/_ext/jsonlexer.py @@ -2,7 +2,8 @@ def setup(app): # enable Pygments json lexer try: import pygments - if pygments.__version__ >= '1.5': + + if pygments.__version__ >= "1.5": # use JSON lexer included in recent versions of Pygments from pygments.lexers import JsonLexer else: @@ -11,4 +12,4 @@ def setup(app): except ImportError: pass # not fatal if we have old (or no) Pygments and no pygments-json else: - app.add_lexer('json', JsonLexer()) + app.add_lexer("json", JsonLexer()) diff --git a/doc/api/api1.rst b/doc/api/api1.rst index 8b68c2220..2deef84c2 100644 --- a/doc/api/api1.rst +++ b/doc/api/api1.rst @@ -180,23 +180,16 @@ the toplist: "title": "FLOSS Weekly", "description": "Free, Libre and Open Source Software with Leo.", "subscribers": 4711, - "subscribers_last_week": 4700 }, {"url": "http://feeds.feedburner.com/LinuxOutlaws", "title": "The Linux Outlaws", "description": "A podcast about Linux with Dan and Fab.", "subscribers": 1337, - "subscribers_last_week": 1330, }] All shown keys must be provided by the server. The ``description`` field may be set to the empty string in case a description is not available. The ``title`` -field may be set to the URL in case a title is not available. The -``subscribers_last_week`` field may be set to zero if no data is available. The -client can use the ``subscribers_last_week`` counts to re-sort the list and get -a ranking for the last week. With this information, a relative "position -movement" can also be calculated if the developer of the client decides to do -so. +field may be set to the URL in case a title is not available. Downloading podcast suggestions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/api/index.rst b/doc/api/index.rst index 736f6088e..6f2b76865 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -25,6 +25,9 @@ compatibility. See :doc:`changes` for a list of changes. The current version is 2.11. This versioning scheme has been introduced in `bug 1273 `_. +The API is available as a machine-readable file in OpenAPI format at +https://raw.githubusercontent.com/gpodder/mygpo/master/mygpo/api/openapi.yaml + Contents -------- diff --git a/doc/api/reference/directory.rst b/doc/api/reference/directory.rst index 2b54b02c4..b1215db9d 100644 --- a/doc/api/reference/directory.rst +++ b/doc/api/reference/directory.rst @@ -113,11 +113,10 @@ Retrieve Podcast Data "title": "Coverville", "author": "Brian Ibbott", "url": "http://feeds.feedburner.com/coverville", - "subscribers_last_week": 19, "logo_url": "http://www.coverville.com/art/coverville_iTunes300.jpg" } - ::query url: the feed URL of the podcast + :query url: the feed URL of the podcast .. _api-episode-data: @@ -150,8 +149,8 @@ Retrieve Episode Data "mygpo_link": "http://gpodder.net/episode/1046492" } - ::query podcast: feed URL of the podcast to which the episode belongs - ::query url: media URL of the episode + :query podcast: feed URL of the podcast to which the episode belongs + :query url: media URL of the episode Podcast Toplist @@ -183,7 +182,6 @@ Podcast Toplist "author": "Sixgun Productions", "url": "http://feeds.feedburner.com/linuxoutlaws", "position_last_week": 0, - "subscribers_last_week": 1736, "subscribers": 1736, "mygpo_link": "http://www.gpodder.net/podcast/11092", "logo_url": "http://linuxoutlaws.com/files/albumart-itunes.jpg" @@ -195,7 +193,6 @@ Podcast Toplist "author": "Leo Laporte", "url": "http://feeds.twit.tv/floss_video_large", "position_last_week": 0, - "subscribers_last_week": 50, "subscribers": 50, "mygpo_link": "http://www.gpodder.net/podcast/31991", "logo_url": "http://static.mediafly.com/publisher/images/06cecab60c784f9d9866f5dcb73227c3/icon-150x150.png" @@ -223,12 +220,7 @@ Podcast Toplist All shown keys must be provided by the server. The description field may be set to the empty string in case a description is not available. The title - field may be set to the URL in case a title is not available. The - subscribers_last_week field may be set to zero if no data is available. The - client can use the subscribers_last_week counts to re-sort the list and get - a ranking for the last week. With this information, a relative "position - movement" can also be calculated if the developer of the client decides to - do so. + field may be set to the URL in case a title is not available. Podcast Search diff --git a/doc/api/reference/general.rst b/doc/api/reference/general.rst index b5f358309..82da3a5f2 100644 --- a/doc/api/reference/general.rst +++ b/doc/api/reference/general.rst @@ -105,7 +105,6 @@ JSON "author": "Sixgun Productions", "url": "http://feeds.feedburner.com/linuxoutlaws", "position_last_week": 1, - "subscribers_last_week": 1943, "subscribers": 1954, "mygpo_link": "http://gpodder.net/podcast/11092", "logo_url": "http://sixgun.org/files/linuxoutlaws.jpg", @@ -128,7 +127,6 @@ XML Sixgun Productions The hardest-hitting Linux podcast around 1954 - 1943 http://sixgun.org/files/linuxoutlaws.jpg http://gpodder.net/logo/64/fa9fd87a4f9e488096e52839450afe0b120684b4.jpg diff --git a/doc/api/reference/suggestions.rst b/doc/api/reference/suggestions.rst index cd229490b..d67022abe 100644 --- a/doc/api/reference/suggestions.rst +++ b/doc/api/reference/suggestions.rst @@ -31,7 +31,6 @@ Retrieve Suggested Podcasts "title": "Linux Geekdom", "author": "aj@linuxgeekdom.com (A.J. Stringham)", "url": "http://www.linuxgeekdom.com/rssmp3.xml", - "subscribers_last_week": 0, "logo_url": null }, { @@ -42,7 +41,6 @@ Retrieve Suggested Podcasts "title": "Going Linux", "author": "Larry Bushey", "url": "http://goinglinux.com/mp3podcast.xml", - "subscribers_last_week": 571, "logo_url": "http://goinglinux.com/images/GoingLinux80.png" }] diff --git a/doc/conf.py b/doc/conf.py index ad7a9ef77..9f3a8a336 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -16,197 +16,200 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -sys.path.insert(0, os.path.abspath('_ext')) -extensions = ['jsonlexer', 'sphinxcontrib.httpdomain'] +sys.path.insert(0, os.path.abspath("_ext")) +extensions = ["jsonlexer", "sphinxcontrib.httpdomain"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'gpodder.net' -copyright = u'2013, gpodder.net Team' +project = u"gpodder.net" +copyright = u"2013, gpodder.net Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '' +version = "" # The full version, including alpha/beta/rc tags. -release = '' +release = "" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'gpoddernetdoc' +htmlhelp_basename = "gpoddernetdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'gpoddernet.tex', u'gpodder.net Documentation', - u'gpodder.net Team', 'manual'), + ( + "index", + "gpoddernet.tex", + u"gpodder.net Documentation", + u"gpodder.net Team", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -214,12 +217,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'gpoddernet', u'gpodder.net Documentation', - [u'gpodder.net Team'], 1) + ("index", "gpoddernet", u"gpodder.net Documentation", [u"gpodder.net Team"], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -228,16 +230,22 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'gpoddernet', u'gpodder.net API Documentation', - u'gpodder.net Team', 'gpoddernet', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "gpoddernet", + u"gpodder.net API Documentation", + u"gpodder.net Team", + "gpoddernet", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' diff --git a/doc/dev/installation.rst b/doc/dev/installation.rst index e2d4ca551..53bde9b1e 100644 --- a/doc/dev/installation.rst +++ b/doc/dev/installation.rst @@ -16,19 +16,27 @@ development version from the repository. Basic setup ----------- +mygpo itself can be cloned from the repository: + +.. code-block:: bash + + git clone git://github.com/gpodder/mygpo.git + cd mygpo + + On a Debian/Ubuntu based system, you can install dependencies with .. code-block:: bash make install-deps - -mygpo itself can be cloned from the repository: +Or: .. code-block:: bash - git clone git://github.com/gpodder/mygpo.git - cd mygpo + sudo apt-get install libpq-dev libjpeg-dev zlib1g-dev libwebp-dev \ + build-essential python3-dev virtualenv libffi-dev redis postgresql + Now install additional dependencies locally: diff --git a/doc/user/clients.rst b/doc/user/clients.rst index 9d77f909c..b74e9c861 100644 --- a/doc/user/clients.rst +++ b/doc/user/clients.rst @@ -8,22 +8,23 @@ Clients Android ------- -==================== ========== ===================================== ======== ================================================== -Name Status gpodder.net features License Documentation -==================== ========== ===================================== ======== ================================================== -`AntennaPod`_ Active Directory, Search, Subscription sync MIT +==================== ========== ========================================================== ======== ================================================== +Name Status gpodder.net features License Documentation +==================== ========== ========================================================== ======== ================================================== +`AntennaPod`_ Active Directory, Search, Subscription sync, Episode Action sync MIT +`Escapepod`_ Active Search MIT `Listen Up Free`_ Active sync, subscription, search -`Podcast Addict`_ Active none `Podcast Addict export script`_ +`Podcast Addict`_ Active none `Podcast Addict export script`_ `Podkicker`_ Active none -`Podax`_ Abandoned Subscription sync BSD -`Volksempfänger`_ Abandoned Podcast search ISC -`SwallowCatcher`_ Abandoned Planned AGPL -`Detlef Gpodderson`_ Abandoned Subscription and Episode Action sync GPL `Detlef Repository`_ -`gpodroid`_ Abandoned Subscriptions only EPL `gpodroid Repository`_ +`Podax`_ Abandoned Subscription sync BSD +`Volksempfänger`_ Abandoned Podcast search ISC +`SwallowCatcher`_ Abandoned Planned AGPL +`Detlef Gpodderson`_ Abandoned Subscription and Episode Action sync GPL `Detlef Repository`_ +`gpodroid`_ Abandoned Subscriptions only EPL `gpodroid Repository`_ `Podstars`_ Abandoned Podcast search -`Podcatcher Deluxe`_ Deprecated Sync of subscriptions and actions GPL 3 -`SoundWaves`_ Abandoned Subscriptions sync GPL 3 -==================== ========== ===================================== ======== ================================================== +`Podcatcher Deluxe`_ Deprecated Sync of subscriptions and actions GPL 3 +`SoundWaves`_ Abandoned Subscriptions sync GPL 3 +==================== ========== ========================================================== ======== ================================================== Mac OS X @@ -139,6 +140,7 @@ Nokia Podcasting - Subscription service GPL `Podcasting D .. _Detlef Gpodderson: https://play.google.com/apps/testing/at.ac.tuwien.detlef .. _Detlef Repository: https://github.com/gpodder/detlef .. _AntennaPod: http://antennapod.org/ +.. _Escapepod: https://github.com/y20k/escapepod .. _gpodroid: https://play.google.com/store/apps/details?id=com.unitedcoders.android.gpodroid .. _gpodroid Repository: https://github.com/gpodder/GpodRoid .. _Podstars: https://play.google.com/store/apps/details?id=com.miga.podstars diff --git a/manage.py b/manage.py index 16c06cc41..81d625f10 100644 --- a/manage.py +++ b/manage.py @@ -5,4 +5,5 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mygpo.settings") from django.core.management import execute_from_command_line + execute_from_command_line(sys.argv) diff --git a/mygpo/administration/auth.py b/mygpo/administration/auth.py index 5bf1d28b0..a6b729f20 100644 --- a/mygpo/administration/auth.py +++ b/mygpo/administration/auth.py @@ -10,13 +10,13 @@ def wrapper(request, *args, **kwargs): staff_token = settings.STAFF_TOKEN token_auth = staff_token is not None and staff_token == request.GET.get( - 'staff', None + "staff", None ) if token_auth: return protected_view(request, *args, **kwargs) if not request.user.is_authenticated: - return HttpResponseRedirect('/login/') + return HttpResponseRedirect("/login/") if request.user.is_staff: return protected_view(request, *args, **kwargs) diff --git a/mygpo/administration/clients.py b/mygpo/administration/clients.py index eb5ebcae5..34123108d 100644 --- a/mygpo/administration/clients.py +++ b/mygpo/administration/clients.py @@ -4,7 +4,7 @@ from mygpo.users.models import Client -Client = namedtuple('Client', 'client client_version lib lib_version os os_version') +Client = namedtuple("Client", "client client_version lib lib_version os os_version") class UserAgentStats(object): @@ -15,8 +15,8 @@ def __init__(self): def get_entries(self): if self._useragents is None: - result = Client.objects.values('user_agent').annotate(Count('user_agent')) - result = {x['user_agent']: x['user_agent__count'] for x in result} + result = Client.objects.values("user_agent").annotate(Count("user_agent")) + result = {x["user_agent"]: x["user_agent__count"] for x in result} self._useragents = Counter(result) return self._useragents @@ -40,13 +40,13 @@ def total_users(self): # regular expressions for detecting a client application from a User-Agent RE_GPODROID = re.compile( - r'GpodRoid ([0-9.]+) Mozilla/5.0 \(Linux; U; Android ([0-9a-z.-]+);' + r"GpodRoid ([0-9.]+) Mozilla/5.0 \(Linux; U; Android ([0-9a-z.-]+);" ) -RE_GPODDER = re.compile(r'mygpoclient/([0-9.]+) \([^)]+\) gPodder/([0-9.]+)') -RE_MYGPOCLIENT = re.compile(r'mygpoclient/([0-9.]+) \([^)]+\)') -RE_CLEMENTINE = re.compile(r'Clementine ([0-9a-z.-]+)') -RE_AMAROK = re.compile(r'amarok/([0-9.]+)') -RE_GPNACCOUNT = re.compile(r'GPodder.net Account for Android') +RE_GPODDER = re.compile(r"mygpoclient/([0-9.]+) \([^)]+\) gPodder/([0-9.]+)") +RE_MYGPOCLIENT = re.compile(r"mygpoclient/([0-9.]+) \([^)]+\)") +RE_CLEMENTINE = re.compile(r"Clementine ([0-9a-z.-]+)") +RE_AMAROK = re.compile(r"amarok/([0-9.]+)") +RE_GPNACCOUNT = re.compile(r"GPodder.net Account for Android") class ClientStats(UserAgentStats): @@ -72,28 +72,28 @@ def parse_ua_string(self, ua_string): m = RE_GPODROID.search(ua_string) if m: - return Client('gpodroid', m.group(1), None, None, 'android', m.group(2)) + return Client("gpodroid", m.group(1), None, None, "android", m.group(2)) m = RE_GPODDER.search(ua_string) if m: - return Client('gpodder', m.group(2), 'mygpoclient', m.group(1), None, None) + return Client("gpodder", m.group(2), "mygpoclient", m.group(1), None, None) m = RE_MYGPOCLIENT.search(ua_string) if m: - return Client(None, None, 'mygpoclient', m.group(1), None, None) + return Client(None, None, "mygpoclient", m.group(1), None, None) m = RE_CLEMENTINE.search(ua_string) if m: - return Client('clementine', m.group(1), None, None, None, None) + return Client("clementine", m.group(1), None, None, None, None) m = RE_AMAROK.search(ua_string) if m: - return Client('amarok', m.group(1), None, None, None, None) + return Client("amarok", m.group(1), None, None, None, None) m = RE_GPNACCOUNT.search(ua_string) if m: return Client( - None, None, 'gpodder.net-account-android', None, 'android', None + None, None, "gpodder.net-account-android", None, "android", None ) return None diff --git a/mygpo/administration/group.py b/mygpo/administration/group.py index 09d3bf220..ed696a71a 100644 --- a/mygpo/administration/group.py +++ b/mygpo/administration/group.py @@ -7,16 +7,16 @@ class PodcastGrouper(object): - """ Groups episodes of two podcasts based on certain features + """Groups episodes of two podcasts based on certain features - The results are sorted by release timestamp """ + The results are sorted by release timestamp""" DEFAULT_RELEASE = datetime(1970, 1, 1) def __init__(self, podcasts): if not podcasts or (None in podcasts): - raise ValueError('podcasts must not be None') + raise ValueError("podcasts must not be None") self.podcasts = podcasts diff --git a/mygpo/administration/tasks.py b/mygpo/administration/tasks.py index f2d73d94c..eab9a5896 100644 --- a/mygpo/administration/tasks.py +++ b/mygpo/administration/tasks.py @@ -16,17 +16,17 @@ def merge_podcasts(podcast_ids, num_groups): """ Task to merge some podcasts""" - logger.info('merging podcast ids %s', podcast_ids) + logger.info("merging podcast ids %s", podcast_ids) podcasts = list(Podcast.objects.filter(id__in=podcast_ids)) - logger.info('merging podcasts %s', podcasts) + logger.info("merging podcasts %s", podcasts) actions = Counter() pm = PodcastMerger(podcasts, actions, num_groups) podcast = pm.merge() - logger.info('merging result: %s', actions) + logger.info("merging result: %s", actions) return actions, podcast diff --git a/mygpo/administration/tests.py b/mygpo/administration/tests.py index ec17c610c..cab3224a5 100644 --- a/mygpo/administration/tests.py +++ b/mygpo/administration/tests.py @@ -22,50 +22,50 @@ class SimpleTest(TestCase): def test_merge(self): p1 = Podcast.objects.get_or_create_for_url( - 'http://example.com/podcast1.rss' + "http://example.com/podcast1.rss" ).object p2 = Podcast.objects.get_or_create_for_url( - 'http://example.com/podcast2.rss' + "http://example.com/podcast2.rss" ).object e1 = Episode.objects.get_or_create_for_url( - p1, 'http://example.com/podcast1/e1.mp3' + p1, "http://example.com/podcast1/e1.mp3" ).object - e1.title = 'Episode 1' + e1.title = "Episode 1" e1.save() e2 = Episode.objects.get_or_create_for_url( - p2, 'http://example.com/podcast1/e2.mp3' + p2, "http://example.com/podcast1/e2.mp3" ).object - e2.title = 'Episode 2' + e2.title = "Episode 2" e2.save() e3 = Episode.objects.get_or_create_for_url( - p2, 'http://example.com/podcast2/e2.mp3' + p2, "http://example.com/podcast2/e2.mp3" ).object - e3.title = 'Episode 3' + e3.title = "Episode 3" e3.save() e4 = Episode.objects.get_or_create_for_url( - p2, 'http://example.com/podcast2/e3.mp3' + p2, "http://example.com/podcast2/e3.mp3" ).object - e4.title = 'Episode 4' + e4.title = "Episode 4" e4.save() User = get_user_model() user = User() - user.username = 'user-test_merge' - user.email = 'user-test_merge@example.com' - user.set_password('secret') + user.username = "user-test_merge" + user.email = "user-test_merge@example.com" + user.set_password("secret") user.save() - device1 = Client.objects.create(user=user, uid='dev1', id=uuid.uuid1()) - device2 = Client.objects.create(user=user, uid='dev2', id=uuid.uuid1()) + device1 = Client.objects.create(user=user, uid="dev1", id=uuid.uuid1()) + device2 = Client.objects.create(user=user, uid="dev2", id=uuid.uuid1()) - subscribe(p1, user, device1) - unsubscribe(p1, user, device1) - subscribe(p1, user, device1) - subscribe(p2, user, device2) + subscribe(p1.pk, user.pk, device1.uid) + unsubscribe(p1.pk, user.pk, device1.uid) + subscribe(p1.pk, user.pk, device1.uid) + subscribe(p2.pk, user.pk, device2.uid) action1 = EpisodeHistoryEntry.create_entry(user, e1, EpisodeHistoryEntry.PLAY) action3 = EpisodeHistoryEntry.create_entry(user, e3, EpisodeHistoryEntry.PLAY) diff --git a/mygpo/administration/urls.py b/mygpo/administration/urls.py index 0016329f0..f0ba2a1a4 100644 --- a/mygpo/administration/urls.py +++ b/mygpo/administration/urls.py @@ -4,40 +4,40 @@ urlpatterns = [ - path('', views.Overview.as_view(), name='admin-overview'), - path('hostinfo', views.HostInfo.as_view(), name='admin-hostinfo'), - path('merge/', views.MergeSelect.as_view(), name='admin-merge'), - path('merge/verify', views.MergeVerify.as_view(), name='admin-merge-verify'), - path('merge/process', views.MergeProcess.as_view(), name='admin-merge-process'), + path("", views.Overview.as_view(), name="admin-overview"), + path("hostinfo", views.HostInfo.as_view(), name="admin-hostinfo"), + path("merge/", views.MergeSelect.as_view(), name="admin-merge"), + path("merge/verify", views.MergeVerify.as_view(), name="admin-merge-verify"), + path("merge/process", views.MergeProcess.as_view(), name="admin-merge-process"), path( - 'merge/status/', + "merge/status/", views.MergeStatus.as_view(), - name='admin-merge-status', + name="admin-merge-status", ), - path('clients', views.ClientStatsView.as_view(), name='clients'), - path('clients.json', views.ClientStatsJsonView.as_view(), name='clients-json'), - path('clients/user_agents', views.UserAgentStatsView.as_view(), name='useragents'), - path('stats', views.StatsView.as_view(), name='stats'), - path('stats.json', views.StatsJsonView.as_view(), name='stats-json'), - path('activate-user', views.ActivateUserView.as_view(), name='admin-activate-user'), + path("clients", views.ClientStatsView.as_view(), name="clients"), + path("clients.json", views.ClientStatsJsonView.as_view(), name="clients-json"), + path("clients/user_agents", views.UserAgentStatsView.as_view(), name="useragents"), + path("stats", views.StatsView.as_view(), name="stats"), + path("stats.json", views.StatsJsonView.as_view(), name="stats-json"), + path("activate-user", views.ActivateUserView.as_view(), name="admin-activate-user"), path( - 'resend-activation-email', + "resend-activation-email", views.ResendActivationEmail.as_view(), - name='admin-resend-activation', + name="admin-resend-activation", ), path( - 'make-publisher/input', + "make-publisher/input", views.MakePublisherInput.as_view(), - name='admin-make-publisher-input', + name="admin-make-publisher-input", ), path( - 'make-publisher/process', + "make-publisher/process", views.MakePublisher.as_view(), - name='admin-make-publisher', + name="admin-make-publisher", ), path( - 'make-publisher/result', + "make-publisher/result", views.MakePublisher.as_view(), - name='admin-make-publisher-result', + name="admin-make-publisher-result", ), ] diff --git a/mygpo/administration/views.py b/mygpo/administration/views.py index 5c94c0abb..00531c07a 100644 --- a/mygpo/administration/views.py +++ b/mygpo/administration/views.py @@ -48,13 +48,13 @@ def dispatch(self, *args, **kwargs): class Overview(AdminView): - template_name = 'admin/overview.html' + template_name = "admin/overview.html" class HostInfo(AdminView): """ shows host information for diagnosis """ - template_name = 'admin/hostinfo.html' + template_name = "admin/hostinfo.html" def get(self, request): commit, msg = get_git_head() @@ -68,31 +68,31 @@ def get(self, request): return self.render_to_response( { - 'git_commit': commit, - 'git_msg': msg, - 'base_dir': base_dir, - 'hostname': hostname, - 'django_version': django_version, - 'num_celery_tasks': self._get_waiting_celery_tasks(), - 'avg_podcast_update_duration': avg_podcast_update_duration, - 'feed_queue_status': feed_queue_status, - 'num_index_outdated': num_index_outdated, + "git_commit": commit, + "git_msg": msg, + "base_dir": base_dir, + "hostname": hostname, + "django_version": django_version, + "num_celery_tasks": self._get_waiting_celery_tasks(), + "avg_podcast_update_duration": avg_podcast_update_duration, + "feed_queue_status": feed_queue_status, + "num_index_outdated": num_index_outdated, } ) def _get_waiting_celery_tasks(self): con = celery.broker_connection() - args = {'host': con.hostname} + args = {"host": con.hostname} if con.port: - args['port'] = con.port + args["port"] = con.port r = redis.StrictRedis(**args) - return r.llen('celery') + return r.llen("celery") def _get_avg_podcast_update_duration(self): queryset = PodcastUpdateResult.objects.filter(successful=True) - return queryset.aggregate(avg_duration=Avg('duration'))['avg_duration'] + return queryset.aggregate(avg_duration=Avg("duration"))["avg_duration"] def _get_feed_queue_status(self): now = datetime.utcnow() @@ -107,20 +107,20 @@ def _get_num_outdated_search_index(self): class MergeSelect(AdminView): - template_name = 'admin/merge-select.html' + template_name = "admin/merge-select.html" def get(self, request): - num = int(request.GET.get('podcasts', 2)) - urls = [''] * num + num = int(request.GET.get("podcasts", 2)) + urls = [""] * num - return self.render_to_response({'urls': urls}) + return self.render_to_response({"urls": urls}) class MergeBase(AdminView): def _get_podcasts(self, request): podcasts = [] for n in count(): - podcast_url = request.POST.get('feed%d' % n, None) + podcast_url = request.POST.get("feed%d" % n, None) if podcast_url is None: break @@ -135,7 +135,7 @@ def _get_podcasts(self, request): class MergeVerify(MergeBase): - template_name = 'admin/merge-grouping.html' + template_name = "admin/merge-grouping.html" def post(self, request): @@ -151,17 +151,17 @@ def get_features(id_id): num_groups = grouper.group(get_features) except InvalidPodcast as ip: - messages.error(request, _('No podcast with URL {url}').format(url=str(ip))) + messages.error(request, _("No podcast with URL {url}").format(url=str(ip))) podcasts = [] num_groups = [] - return self.render_to_response({'podcasts': podcasts, 'groups': num_groups}) + return self.render_to_response({"podcasts": podcasts, "groups": num_groups}) class MergeProcess(MergeBase): - RE_EPISODE = re.compile(r'episode_([0-9a-fA-F]{32})') + RE_EPISODE = re.compile(r"episode_([0-9a-fA-F]{32})") def post(self, request): @@ -169,7 +169,7 @@ def post(self, request): podcasts = self._get_podcasts(request) except InvalidPodcast as ip: - messages.error(request, _('No podcast with URL {url}').format(url=str(ip))) + messages.error(request, _("No podcast with URL {url}").format(url=str(ip))) grouper = PodcastGrouper(podcasts) @@ -184,14 +184,14 @@ def post(self, request): num_groups = grouper.group(get_features) - if 'renew' in request.POST: + if "renew" in request.POST: return render( request, - 'admin/merge-grouping.html', - {'podcasts': podcasts, 'groups': num_groups}, + "admin/merge-grouping.html", + {"podcasts": podcasts, "groups": num_groups}, ) - elif 'merge' in request.POST: + elif "merge" in request.POST: podcast_ids = [p.get_id() for p in podcasts] num_groups = list(num_groups) @@ -199,20 +199,20 @@ def post(self, request): res = merge_podcasts.delay(podcast_ids, num_groups) return HttpResponseRedirect( - reverse('admin-merge-status', args=[res.task_id]) + reverse("admin-merge-status", args=[res.task_id]) ) class MergeStatus(AdminView): """ Displays the status of the merge operation """ - template_name = 'admin/task-status.html' + template_name = "admin/task-status.html" def get(self, request, task_id): result = merge_podcasts.AsyncResult(task_id) if not result.ready(): - return self.render_to_response({'ready': False}) + return self.render_to_response({"ready": False}) # clear cache to make merge result visible # TODO: what to do with multiple frontends? @@ -223,15 +223,15 @@ def get(self, request, task_id): except IncorrectMergeException as ime: messages.error(request, str(ime)) - return HttpResponseRedirect(reverse('admin-merge')) + return HttpResponseRedirect(reverse("admin-merge")) return self.render_to_response( - {'ready': True, 'actions': actions.items(), 'podcast': podcast} + {"ready": True, "actions": actions.items(), "podcast": podcast} ) class UserAgentStatsView(AdminView): - template_name = 'admin/useragents.html' + template_name = "admin/useragents.html" def get(self, request): @@ -240,15 +240,15 @@ def get(self, request): return self.render_to_response( { - 'useragents': useragents.most_common(), - 'max_users': uas.max_users, - 'total': uas.total_users, + "useragents": useragents.most_common(), + "max_users": uas.max_users, + "total": uas.total_users, } ) class ClientStatsView(AdminView): - template_name = 'admin/clients.html' + template_name = "admin/clients.html" def get(self, request): @@ -257,9 +257,9 @@ def get(self, request): return self.render_to_response( { - 'clients': clients.most_common(), - 'max_users': cs.max_users, - 'total': cs.total_users, + "clients": clients.most_common(), + "max_users": cs.max_users, + "total": cs.total_users, } ) @@ -284,18 +284,18 @@ def to_dict(self, res): class StatsView(AdminView): """ shows general stats as HTML page """ - template_name = 'admin/stats.html' + template_name = "admin/stats.html" def _get_stats(self): return { - 'podcasts': Podcast.objects.count_fast(), - 'episodes': Episode.objects.count_fast(), - 'users': UserProxy.objects.count_fast(), + "podcasts": Podcast.objects.count_fast(), + "episodes": Episode.objects.count_fast(), + "users": UserProxy.objects.count_fast(), } def get(self, request): stats = self._get_stats() - return self.render_to_response({'stats': stats}) + return self.render_to_response({"stats": stats}) class StatsJsonView(StatsView): @@ -309,92 +309,92 @@ def get(self, request): class ActivateUserView(AdminView): """ Lets admins manually activate users """ - template_name = 'admin/activate-user.html' + template_name = "admin/activate-user.html" def get(self, request): return self.render_to_response({}) def post(self, request): - username = request.POST.get('username') - email = request.POST.get('email') + username = request.POST.get("username") + email = request.POST.get("email") if not (username or email): - messages.error(request, _('Provide either username or email address')) - return HttpResponseRedirect(reverse('admin-activate-user')) + messages.error(request, _("Provide either username or email address")) + return HttpResponseRedirect(reverse("admin-activate-user")) try: user = UserProxy.objects.all().by_username_or_email(username, email) except UserProxy.DoesNotExist: - messages.error(request, _('No user found')) - return HttpResponseRedirect(reverse('admin-activate-user')) + messages.error(request, _("No user found")) + return HttpResponseRedirect(reverse("admin-activate-user")) user.activate() messages.success( request, _( - 'User {username} ({email}) activated'.format( + "User {username} ({email}) activated".format( username=user.username, email=user.email ) ), ) - return HttpResponseRedirect(reverse('admin-activate-user')) + return HttpResponseRedirect(reverse("admin-activate-user")) class ResendActivationEmail(AdminView): """ Resends the users activation email """ - template_name = 'admin/resend-acivation.html' + template_name = "admin/resend-acivation.html" def get(self, request): return self.render_to_response({}) def post(self, request): - username = request.POST.get('username') - email = request.POST.get('email') + username = request.POST.get("username") + email = request.POST.get("email") if not (username or email): - messages.error(request, _('Provide either username or email address')) - return HttpResponseRedirect(reverse('admin-resend-activation')) + messages.error(request, _("Provide either username or email address")) + return HttpResponseRedirect(reverse("admin-resend-activation")) try: user = UserProxy.objects.all().by_username_or_email(username, email) except UserProxy.DoesNotExist: - messages.error(request, _('No user found')) - return HttpResponseRedirect(reverse('admin-resend-activation')) + messages.error(request, _("No user found")) + return HttpResponseRedirect(reverse("admin-resend-activation")) if user.is_active: - messages.success(request, 'User {username} is already activated') + messages.success(request, "User {username} is already activated") else: send_activation_email(user, request) messages.success( request, _( - 'Email for {username} ({email}) resent'.format( + "Email for {username} ({email}) resent".format( username=user.username, email=user.email ) ), ) - return HttpResponseRedirect(reverse('admin-resend-activation')) + return HttpResponseRedirect(reverse("admin-resend-activation")) class MakePublisherInput(AdminView): """ Get all information necessary for making someone publisher """ - template_name = 'admin/make-publisher-input.html' + template_name = "admin/make-publisher-input.html" class MakePublisher(AdminView): """ Assign publisher permissions """ - template_name = 'admin/make-publisher-result.html' + template_name = "admin/make-publisher-result.html" def post(self, request): User = get_user_model() - username = request.POST.get('username') + username = request.POST.get("username") try: user = User.objects.get(username__iexact=username) @@ -402,9 +402,9 @@ def post(self, request): messages.error( request, 'User "{username}" not found'.format(username=username) ) - return HttpResponseRedirect(reverse('admin-make-publisher-input')) + return HttpResponseRedirect(reverse("admin-make-publisher-input")) - feeds = request.POST.get('feeds') + feeds = request.POST.get("feeds") feeds = feeds.split() podcasts = set() @@ -413,7 +413,7 @@ def post(self, request): podcast = Podcast.objects.get(urls__url=feed) except Podcast.DoesNotExist: messages.warning( - request, 'Podcast with URL {feed} not found'.format(feed=feed) + request, "Podcast with URL {feed} not found".format(feed=feed) ) continue @@ -423,30 +423,30 @@ def post(self, request): if (created + existed) > 0: self.send_mail(request, user, podcasts) - return HttpResponseRedirect(reverse('admin-make-publisher-result')) + return HttpResponseRedirect(reverse("admin-make-publisher-result")) def set_publisher(self, request, user, podcasts): created, existed = PublishedPodcast.objects.publish_podcasts(user, podcasts) messages.success( request, - 'Set publisher permissions for {created} podcasts; ' - '{existed} already existed'.format(created=created, existed=existed), + "Set publisher permissions for {created} podcasts; " + "{existed} already existed".format(created=created, existed=existed), ) return created, existed def send_mail(self, request, user, podcasts): site = RequestSite(request) msg = render_to_string( - 'admin/make-publisher-mail.txt', + "admin/make-publisher-mail.txt", { - 'user': user, - 'podcasts': podcasts, - 'support_url': settings.SUPPORT_URL, - 'site': site, + "user": user, + "podcasts": podcasts, + "support_url": settings.SUPPORT_URL, + "site": site, }, request=request, ) - subj = get_email_subject(site, _('Publisher Permissions')) + subj = get_email_subject(site, _("Publisher Permissions")) user.email_user(subj, msg) messages.success( @@ -455,8 +455,8 @@ def send_mail(self, request, user, podcasts): class MakePublisherResult(AdminView): - template_name = 'make-publisher-result.html' + template_name = "make-publisher-result.html" def get_email_subject(site, txt): - return '[{domain}] {txt}'.format(domain=site.domain, txt=txt) + return "[{domain}] {txt}".format(domain=site.domain, txt=txt) diff --git a/mygpo/api/__init__.py b/mygpo/api/__init__.py index 6605b1ac9..a830afcbe 100644 --- a/mygpo/api/__init__.py +++ b/mygpo/api/__init__.py @@ -43,22 +43,22 @@ def parsed_body(self, request): """ Returns the object parsed from the JSON request body """ if not request.body: - raise RequestException('POST data must not be empty') + raise RequestException("POST data must not be empty") try: # TODO: implementation of parse_request_body can be moved here # after all views using it have been refactored return parse_request_body(request) except (UnicodeDecodeError, ValueError): - msg = 'Could not decode request body for user {}: {}'.format( - request.user.username, request.body.decode('ascii', errors='replace') + msg = "Could not decode request body for user {}: {}".format( + request.user.username, request.body.decode("ascii", errors="replace") ) logger.warning(msg, exc_info=True) raise RequestException(msg) def get_since(self, request): """ Returns parsed "since" GET parameter """ - since_ = request.GET.get('since', None) + since_ = request.GET.get("since", None) if since_ is None: raise RequestException("parameter 'since' missing") diff --git a/mygpo/api/advanced/__init__.py b/mygpo/api/advanced/__init__.py index 7fbf27c1a..ef2c49611 100644 --- a/mygpo/api/advanced/__init__.py +++ b/mygpo/api/advanced/__init__.py @@ -46,14 +46,14 @@ # keys that are allowed in episode actions EPISODE_ACTION_KEYS = ( - 'position', - 'episode', - 'action', - 'device', - 'timestamp', - 'started', - 'total', - 'podcast', + "position", + "episode", + "action", + "device", + "timestamp", + "started", + "total", + "podcast", ) @@ -61,28 +61,28 @@ @require_valid_user @check_username @never_cache -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) @cors_origin() def episodes(request, username, version=1): version = int(version) now = datetime.utcnow() now_ = get_timestamp(now) - ua_string = request.META.get('HTTP_USER_AGENT', '') + ua_string = request.META.get("HTTP_USER_AGENT", "") - if request.method == 'POST': + if request.method == "POST": try: actions = parse_request_body(request) except (UnicodeDecodeError, ValueError) as e: - msg = ('Could not decode episode update POST data for ' + 'user %s: %s') % ( + msg = ("Could not decode episode update POST data for " + "user %s: %s") % ( username, - request.body.decode('ascii', errors='replace'), + request.body.decode("ascii", errors="replace"), ) logger.warning(msg, exc_info=True) return HttpResponseBadRequest(msg) logger.info( - 'start: user %s: %d actions from %s' + "start: user %s: %d actions from %s" % (request.user, len(actions), ua_string) ) @@ -94,20 +94,20 @@ def episodes(request, username, version=1): bg_handler = dsettings.API_ACTIONS_BG_HANDLER if bg_handler is not None: - modname, funname = bg_handler.rsplit('.', 1) + modname, funname = bg_handler.rsplit(".", 1) mod = import_module(modname) fun = getattr(mod, funname) fun(request.user, actions, now, ua_string) # TODO: return 202 Accepted - return JsonResponse({'timestamp': now_, 'update_urls': []}) + return JsonResponse({"timestamp": now_, "update_urls": []}) try: update_urls = update_episodes(request.user, actions, now, ua_string) except ValidationError as e: logger.warning( - 'Validation Error while uploading episode actions ' 'for user %s: %s', + "Validation Error while uploading episode actions " "for user %s: %s", username, str(e), ) @@ -115,30 +115,30 @@ def episodes(request, username, version=1): except InvalidEpisodeActionAttributes as e: msg = ( - 'invalid episode action attributes while uploading episode actions for user %s' + "invalid episode action attributes while uploading episode actions for user %s" % (username,) ) logger.warning(msg, exc_info=True) return HttpResponseBadRequest(str(e)) logger.info( - 'done: user %s: %d actions from %s' + "done: user %s: %d actions from %s" % (request.user, len(actions), ua_string) ) - return JsonResponse({'timestamp': now_, 'update_urls': update_urls}) + return JsonResponse({"timestamp": now_, "update_urls": update_urls}) - elif request.method == 'GET': - podcast_url = request.GET.get('podcast', None) - device_uid = request.GET.get('device', None) - since_ = request.GET.get('since', None) - aggregated = parse_bool(request.GET.get('aggregated', False)) + elif request.method == "GET": + podcast_url = request.GET.get("podcast", None) + device_uid = request.GET.get("device", None) + since_ = request.GET.get("since", None) + aggregated = parse_bool(request.GET.get("aggregated", False)) try: since = int(since_) if since_ else None if since is not None: since = datetime.utcfromtimestamp(since) except ValueError: - return HttpResponseBadRequest('since-value is not a valid timestamp') + return HttpResponseBadRequest("since-value is not a valid timestamp") if podcast_url: podcast = get_object_or_404(Podcast, urls__url=podcast_url) @@ -165,7 +165,7 @@ def episodes(request, username, version=1): def convert_position(action): """ convert position parameter for API 1 compatibility """ - pos = getattr(action, 'position', None) + pos = getattr(action, "position", None) if pos is not None: action.position = format_time(pos) return action @@ -176,7 +176,7 @@ def get_episode_changes(user, podcast, device, since, until, aggregated, version history = EpisodeHistoryEntry.objects.filter(user=user, timestamp__lt=until) # return the earlier entries first - history = history.order_by('timestamp') + history = history.order_by("timestamp") if since: history = history.filter(timestamp__gte=since) @@ -200,32 +200,32 @@ def get_episode_changes(user, podcast, device, since, until, aggregated, version actions = [episode_action_json(a, user) for a in history] if aggregated: - actions = list(dict((a['episode'], a) for a in actions).values()) + actions = list(dict((a["episode"], a) for a in actions).values()) if history: ts = get_timestamp(history[-1].timestamp) else: ts = get_timestamp(until) - return {'actions': actions, 'timestamp': ts} + return {"actions": actions, "timestamp": ts} def episode_action_json(history, user): action = { - 'podcast': history.podcast_ref_url or history.episode.podcast.url, - 'episode': history.episode_ref_url or history.episode.url, - 'action': history.action, - 'timestamp': history.timestamp.isoformat(), + "podcast": history.podcast_ref_url or history.episode.podcast.url, + "episode": history.episode_ref_url or history.episode.url, + "action": history.action, + "timestamp": history.timestamp.isoformat(), } if history.client: - action['device'] = history.client.uid + action["device"] = history.client.uid if history.action == EpisodeHistoryEntry.PLAY: - action['started'] = history.started - action['position'] = history.stopped # TODO: check "playmark" - action['total'] = history.total + action["started"] = history.started + action["position"] = history.stopped # TODO: check "playmark" + action["total"] = history.total return action @@ -236,12 +236,12 @@ def update_episodes(user, actions, now, ua_string): # group all actions by their episode for action in actions: - podcast_url = action.get('podcast', '') + podcast_url = action.get("podcast", "") podcast_url = sanitize_append(podcast_url, update_urls) if not podcast_url: continue - episode_url = action.get('episode', '') + episode_url = action.get("episode", "") episode_url = sanitize_append(episode_url, update_urls) if not episode_url: continue @@ -269,26 +269,26 @@ def update_episodes(user, actions, now, ua_string): def parse_episode_action(action, user, update_urls, now, ua_string): - action_str = action.get('action', None) + action_str = action.get("action", None) if not valid_episodeaction(action_str): - raise Exception('invalid action %s' % action_str) + raise Exception("invalid action %s" % action_str) history = EpisodeHistoryEntry() - history.action = action['action'] + history.action = action["action"] - if action.get('device', False): - client = get_device(user, action['device'], ua_string) + if action.get("device", False): + client = get_device(user, action["device"], ua_string) history.client = client - if action.get('timestamp', False): - history.timestamp = dateutil.parser.parse(action['timestamp']) + if action.get("timestamp", False): + history.timestamp = dateutil.parser.parse(action["timestamp"]) else: history.timestamp = now - history.started = action.get('started', None) - history.stopped = action.get('position', None) - history.total = action.get('total', None) + history.started = action.get("started", None) + history.stopped = action.get("position", None) + history.total = action.get("total", None) return history @@ -299,30 +299,30 @@ def parse_episode_action(action, user, update_urls, now, ua_string): @never_cache # Workaround for mygpoclient 1.0: It uses "PUT" requests # instead of "POST" requests for uploading device settings -@allowed_methods(['POST', 'PUT']) +@allowed_methods(["POST", "PUT"]) @cors_origin() def device(request, username, device_uid, version=None): - d = get_device(request.user, device_uid, request.META.get('HTTP_USER_AGENT', '')) + d = get_device(request.user, device_uid, request.META.get("HTTP_USER_AGENT", "")) try: data = parse_request_body(request) except (UnicodeDecodeError, ValueError) as e: - msg = ('Could not decode device update POST data for ' + 'user %s: %s') % ( + msg = ("Could not decode device update POST data for " + "user %s: %s") % ( username, - request.body.decode('ascii', errors='replace'), + request.body.decode("ascii", errors="replace"), ) logger.warning(msg, exc_info=True) return HttpResponseBadRequest(msg) - if 'caption' in data: - if not data['caption']: - return HttpResponseBadRequest('caption must not be empty') - d.name = data['caption'] + if "caption" in data: + if not data["caption"]: + return HttpResponseBadRequest("caption must not be empty") + d.name = data["caption"] - if 'type' in data: - if not valid_devicetype(data['type']): - return HttpResponseBadRequest('invalid device type %s' % data['type']) - d.type = data['type'] + if "type" in data: + if not valid_devicetype(data["type"]): + return HttpResponseBadRequest("invalid device type %s" % data["type"]) + d.type = data["type"] d.save() return HttpResponse() @@ -346,7 +346,7 @@ def valid_episodeaction(type): @require_valid_user @check_username @never_cache -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cors_origin() def devices(request, username, version=None): user = request.user @@ -379,5 +379,5 @@ def favorites(request, username): def sanitize_append(url, sanitized_list): urls = normalize_feed_url(url) if url != urls: - sanitized_list.append((url, urls or '')) + sanitized_list.append((url, urls or "")) return urls diff --git a/mygpo/api/advanced/auth.py b/mygpo/api/advanced/auth.py index 25ac6b3c1..b47e9f16d 100644 --- a/mygpo/api/advanced/auth.py +++ b/mygpo/api/advanced/auth.py @@ -12,7 +12,7 @@ @csrf_exempt @require_valid_user @check_username -@allowed_methods(['POST']) +@allowed_methods(["POST"]) @never_cache @cors_origin() def login(request, username): @@ -26,7 +26,7 @@ def login(request, username): @csrf_exempt @check_username -@allowed_methods(['POST']) +@allowed_methods(["POST"]) @never_cache @cors_origin() def logout(request, username): diff --git a/mygpo/api/advanced/directory.py b/mygpo/api/advanced/directory.py index c05e38a15..412cc9102 100644 --- a/mygpo/api/advanced/directory.py +++ b/mygpo/api/advanced/directory.py @@ -44,7 +44,7 @@ def tag_podcasts(request, tag, count): domain = RequestSite(request).domain entries = category.entries.all().prefetch_related( - 'podcast', 'podcast__slugs', 'podcast__urls' + "podcast", "podcast__slugs", "podcast__urls" )[:count] resp = [podcast_data(entry.podcast, domain) for entry in entries] return JsonResponse(resp) @@ -53,7 +53,7 @@ def tag_podcasts(request, tag, count): @cache_page(60 * 60) @cors_origin() def podcast_info(request): - url = normalize_feed_url(request.GET.get('url', '')) + url = normalize_feed_url(request.GET.get("url", "")) # 404 before we query for url, because query would complain # about missing param @@ -70,8 +70,8 @@ def podcast_info(request): @cache_page(60 * 60) @cors_origin() def episode_info(request): - podcast_url = normalize_feed_url(request.GET.get('podcast', '')) - episode_url = normalize_feed_url(request.GET.get('url', '')) + podcast_url = normalize_feed_url(request.GET.get("podcast", "")) + episode_url = normalize_feed_url(request.GET.get("url", "")) # 404 before we query for url, because query would complain # about missing parameters @@ -82,7 +82,7 @@ def episode_info(request): query = Episode.objects.filter( podcast__urls__url=podcast_url, urls__url=episode_url ) - episode = query.select_related('podcast').get() + episode = query.select_related("podcast").get() except Episode.DoesNotExist: raise Http404 @@ -93,11 +93,11 @@ def episode_info(request): @csrf_exempt -@allowed_methods(['POST']) +@allowed_methods(["POST"]) @cors_origin() def add_podcast(request): # TODO what if the url doesn't have a valid podcast? - url = normalize_feed_url(json.loads(request.body.decode('utf-8')).get('url', '')) + url = normalize_feed_url(json.loads(request.body.decode("utf-8")).get("url", "")) # 404 before we query for url, because query would complain # about missing param @@ -107,42 +107,42 @@ def add_podcast(request): try: # podcast exists, redirects Podcast.objects.get(urls__url=url) - api_podcast_info_path = reverse('api-podcast-info') - return HttpResponseRedirect(f'{api_podcast_info_path}?url={url}') + api_podcast_info_path = reverse("api-podcast-info") + return HttpResponseRedirect(f"{api_podcast_info_path}?url={url}") except Podcast.DoesNotExist: # podcast doesn't exist, add new podcast res = update_podcasts.delay([url]) response = HttpResponse(status=202) job_status_path = reverse( - 'api-add-podcast-status', kwargs={"job_id": res.task_id} + "api-add-podcast-status", kwargs={"job_id": res.task_id} ) - response['Location'] = f'{job_status_path}?url={url}' + response["Location"] = f"{job_status_path}?url={url}" return response @csrf_exempt -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cors_origin() def add_podcast_status(request, job_id): - url = request.GET.get('url', '') + url = request.GET.get("url", "") result = update_podcasts.AsyncResult(job_id) - resp = {'id': str(job_id), 'type': 'create-podcast', 'url': url} + resp = {"id": str(job_id), "type": "create-podcast", "url": url} if not result.ready(): - resp['status'] = 'pending' + resp["status"] = "pending" elif result.successful(): - resp['status'] = 'successful' - resp['podcast'] = f'/api/2/data/podcast.json?url={url}' + resp["status"] = "successful" + resp["podcast"] = f"/api/2/data/podcast.json?url={url}" elif result.failed(): - resp['status'] = 'unsuccessful' - resp['error'] = 'feed could not be parsed' + resp["status"] = "unsuccessful" + resp["error"] = "feed could not be parsed" return JsonResponse(resp) def podcast_data(obj, domain, scaled_logo_size=64): if obj is None: - raise ValueError('podcast should not be None') + raise ValueError("podcast should not be None") if isinstance(obj, SubscribedPodcast): url = obj.ref_url @@ -152,7 +152,6 @@ def podcast_data(obj, domain, scaled_logo_size=64): url = podcast.url subscribers = podcast.subscribers - last_subscribers = podcast.subscribers scaled_logo_url = get_logo_url(podcast, scaled_logo_size) @@ -162,11 +161,10 @@ def podcast_data(obj, domain, scaled_logo_size=64): "author": podcast.author, "description": podcast.description, "subscribers": subscribers, - "subscribers_last_week": last_subscribers, "logo_url": podcast.logo_url, - "scaled_logo_url": 'http://%s%s' % (domain, scaled_logo_url), + "scaled_logo_url": "http://%s%s" % (domain, scaled_logo_url), "website": podcast.link, - "mygpo_link": 'http://%s%s' % (domain, get_podcast_link_target(podcast)), + "mygpo_link": "http://%s%s" % (domain, get_podcast_link_target(podcast)), } @@ -177,20 +175,20 @@ def episode_data(episode, domain, podcast=None): data = { "title": episode.title, "url": episode.url, - "podcast_title": podcast.title if podcast else '', - "podcast_url": podcast.url if podcast else '', + "podcast_title": podcast.title if podcast else "", + "podcast_url": podcast.url if podcast else "", "description": episode.description, "website": episode.link, - "mygpo_link": 'http://%(domain)s%(res)s' + "mygpo_link": "http://%(domain)s%(res)s" % dict(domain=domain, res=get_episode_link_target(episode, podcast)) if podcast - else '', + else "", } if episode.released: - data['released'] = episode.released.strftime('%Y-%m-%dT%H:%M:%S') + data["released"] = episode.released.strftime("%Y-%m-%dT%H:%M:%S") else: - data['released'] = '' + data["released"] = "" return data diff --git a/mygpo/api/advanced/episode.py b/mygpo/api/advanced/episode.py index da87c7cfe..4c18bcb5d 100644 --- a/mygpo/api/advanced/episode.py +++ b/mygpo/api/advanced/episode.py @@ -16,14 +16,14 @@ def post(self, request, username): body = self.parsed_body(request) podcast_url, episode_url, update_urls = self.get_urls(body) - body['podcast'] = podcast_url - body['episode'] = episode_url + body["podcast"] = podcast_url + body["episode"] = episode_url if not podcast_url or not episode_url: - raise RequestException('Invalid Podcast or Episode URL') + raise RequestException("Invalid Podcast or Episode URL") self.update_chapters(body, user) - return JsonResponse({'update_url': update_urls, 'timestamp': now_}) + return JsonResponse({"update_url": update_urls, "timestamp": now_}) def get(self, request, username): """ Get chapters for an episode """ @@ -44,7 +44,7 @@ def get(self, request, username): chapters_json = map(self.chapter_to_json, chapters) - return JsonResponse({'chapters': chapters_json, 'timestamp': now_}) + return JsonResponse({"chapters": chapters_json, "timestamp": now_}) def update_chapters(self, req, user): """ Add / remove chapters according to the client's request """ @@ -52,12 +52,12 @@ def update_chapters(self, req, user): episode = Episode.objects.get_or_create_for_url(podcast, episode_url).object # add chapters - for chapter_data in req.get('chapters_add', []): + for chapter_data in req.get("chapters_add", []): chapter = self.parse_new(user, chapter_data) chapter.save() # remove chapters - for chapter_data in req.get('chapters_remove', []): + for chapter_data in req.get("chapters_remove", []): start, end = self.parse_rem(chapter_data) Chapter.objects.filter( user=user, episode=episode, start=start, end=end @@ -66,61 +66,61 @@ def update_chapters(self, req, user): def parse_new(self, user, chapter_data): """ Parse a chapter to be added """ chapter = Chapter() - if not 'start' in chapter_data: - raise ParameterMissing('start parameter missing') - chapter.start = parse_time(chapter_data['start']) + if not "start" in chapter_data: + raise ParameterMissing("start parameter missing") + chapter.start = parse_time(chapter_data["start"]) - if not 'end' in chapter_data: - raise ParameterMissing('end parameter missing') - chapter.end = parse_time(chapter_data['end']) + if not "end" in chapter_data: + raise ParameterMissing("end parameter missing") + chapter.end = parse_time(chapter_data["end"]) - chapter.label = chapter_data.get('label', '') - chapter.advertisement = chapter_data.get('advertisement', False) + chapter.label = chapter_data.get("label", "") + chapter.advertisement = chapter_data.get("advertisement", False) return chapter def parse_rem(self, chapter_data): """ Parse a chapter to be removed """ - if not 'start' in chapter_data: - raise ParameterMissing('start parameter missing') - start = parse_time(chapter_data['start']) + if not "start" in chapter_data: + raise ParameterMissing("start parameter missing") + start = parse_time(chapter_data["start"]) - if not 'end' in chapter_data: - raise ParameterMissing('end parameter missing') - end = parse_time(chapter_data['end']) + if not "end" in chapter_data: + raise ParameterMissing("end parameter missing") + end = parse_time(chapter_data["end"]) return (start, end) def get_urls(self, body): """ Parse and normalize the URLs from the request """ - podcast_url = body.get('podcast', '') - episode_url = body.get('episode', '') + podcast_url = body.get("podcast", "") + episode_url = body.get("episode", "") if not podcast_url: - raise RequestException('Podcast URL missing') + raise RequestException("Podcast URL missing") if not episode_url: - raise RequestException('Episode URL missing') + raise RequestException("Episode URL missing") update_urls = [] # podcast sanitizing s_podcast_url = normalize_feed_url(podcast_url) if s_podcast_url != podcast_url: - update_urls.append((podcast_url, s_podcast_url or '')) + update_urls.append((podcast_url, s_podcast_url or "")) # episode sanitizing - s_episode_url = normalize_feed_url(episode_url, 'episode') + s_episode_url = normalize_feed_url(episode_url, "episode") if s_episode_url != episode_url: - update_urls.append((episode_url, s_episode_url or '')) + update_urls.append((episode_url, s_episode_url or "")) return s_podcast_url, s_episode_url, update_urls def chapter_to_json(self, chapter): """ JSON representation of Chapter for GET response """ return { - 'start': chapter.start, - 'end': chapter.end, - 'label': chapter.label, - 'advertisement': chapter.advertisement, - 'timestamp': chapter.created, + "start": chapter.start, + "end": chapter.end, + "label": chapter.label, + "advertisement": chapter.advertisement, + "timestamp": chapter.created, } diff --git a/mygpo/api/advanced/lists.py b/mygpo/api/advanced/lists.py index 9d366e3f7..d057313f2 100644 --- a/mygpo/api/advanced/lists.py +++ b/mygpo/api/advanced/lists.py @@ -28,45 +28,45 @@ @check_username @check_format @never_cache -@allowed_methods(['POST']) +@allowed_methods(["POST"]) @cors_origin() def create(request, username, format): """ Creates a new podcast list and links to it in the Location header """ - title = request.GET.get('title', None) + title = request.GET.get("title", None) if not title: - return HttpResponseBadRequest('Title missing') + return HttpResponseBadRequest("Title missing") slug = slugify(title) if not slug: - return HttpResponseBadRequest('Invalid title') + return HttpResponseBadRequest("Invalid title") plist, created = PodcastList.objects.get_or_create( user=request.user, slug=slug, defaults={ - 'id': uuid.uuid1(), - 'title': title, - 'slug': slug, - 'created': datetime.utcnow(), - 'modified': datetime.utcnow(), + "id": uuid.uuid1(), + "title": title, + "slug": slug, + "created": datetime.utcnow(), + "modified": datetime.utcnow(), }, ) if not created: - return HttpResponse('List already exists', status=409) + return HttpResponse("List already exists", status=409) - urls = parse_subscription(request.body.decode('utf-8'), format) + urls = parse_subscription(request.body.decode("utf-8"), format) podcasts = [Podcast.objects.get_or_create_for_url(url).object for url in urls] for podcast in podcasts: plist.add_entry(podcast) response = HttpResponse(status=201) - list_url = reverse('api-get-list', args=[request.user.username, slug, format]) - response['Location'] = list_url + list_url = reverse("api-get-list", args=[request.user.username, slug, format]) + response["Location"] = list_url return response @@ -75,13 +75,13 @@ def _get_list_data(l, username, domain): return dict( title=l.title, name=l.slug, - web='http://%s%s' % (domain, reverse('list-show', args=[username, l.slug])), + web="http://%s%s" % (domain, reverse("list-show", args=[username, l.slug])), ) @csrf_exempt @never_cache -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cors_origin() def get_lists(request, username): """ Returns a list of all podcast lists by the given user """ @@ -104,7 +104,7 @@ def get_lists(request, username): @csrf_exempt @check_format @never_cache -@allowed_methods(['GET', 'PUT', 'DELETE']) +@allowed_methods(["GET", "PUT", "DELETE"]) @cors_origin() def podcast_list(request, username, slug, format): @@ -118,13 +118,13 @@ def get_list(request, plist, owner, format): """ Returns the contents of the podcast list """ try: - scale = int(request.GET.get('scale_logo', 64)) + scale = int(request.GET.get("scale_logo", 64)) except (TypeError, ValueError): - return HttpResponseBadRequest('scale_logo has to be a numeric value') + return HttpResponseBadRequest("scale_logo has to be a numeric value") domain = RequestSite(request).domain p_data = lambda p: podcast_data(p, domain, scale) - title = '{title} by {username}'.format(title=plist.title, username=owner.username) + title = "{title} by {username}".format(title=plist.title, username=owner.username) objs = [entry.content_object for entry in plist.entries.all()] @@ -133,8 +133,8 @@ def get_list(request, plist, owner, format): format, title, json_map=p_data, - jsonp_padding=request.GET.get('jsonp', ''), - xml_template='podcasts.xml', + jsonp_padding=request.GET.get("jsonp", ""), + xml_template="podcasts.xml", request=request, ) @@ -143,7 +143,7 @@ def get_list(request, plist, owner, format): @cors_origin() def update_list(request, plist, owner, format): """ Replaces the podcasts in the list and returns 204 No Content """ - urls = parse_subscription(request.body.decode('utf-8'), format) + urls = parse_subscription(request.body.decode("utf-8"), format) podcasts = [Podcast.objects.get_or_create_for_url(url).object for url in urls] plist.set_entries(podcasts) diff --git a/mygpo/api/advanced/settings.py b/mygpo/api/advanced/settings.py index e549b95fc..5da7bbf39 100644 --- a/mygpo/api/advanced/settings.py +++ b/mygpo/api/advanced/settings.py @@ -9,9 +9,9 @@ class SettingsAPI(APIView): - """ Settings API + """Settings API - wiki.gpodder.org/wiki/Web_Services/API_2/Settings """ + wiki.gpodder.org/wiki/Web_Services/API_2/Settings""" def get(self, request, username, scope): """ Get settings for scope object """ @@ -31,31 +31,31 @@ def post(self, request, username, scope): def get_scope(self, request, scope): """ Get the scope object """ - if scope == 'account': + if scope == "account": return None - if scope == 'device': - uid = request.GET.get('device', '') + if scope == "device": + uid = request.GET.get("device", "") return request.user.client_set.get(uid=uid) - episode_url = request.GET.get('episode', '') - podcast_url = request.GET.get('podcast', '') + episode_url = request.GET.get("episode", "") + podcast_url = request.GET.get("podcast", "") - if scope == 'podcast': + if scope == "podcast": return get_object_or_404(Podcast, urls__url=podcast_url) - if scope == 'episode': + if scope == "episode": podcast = get_object_or_404(Podcast, urls__url=podcast_url) return get_object_or_404(Episode, podcast=podcast, urls__url=episode_url) - raise RequestException('undefined scope %s' % scope) + raise RequestException("undefined scope %s" % scope) def update_settings(self, settings, actions): """ Update the settings according to the actions """ - for key, value in actions.get('set', {}).items(): + for key, value in actions.get("set", {}).items(): settings.set_setting(key, value) - for key in actions.get('remove', []): + for key in actions.get("remove", []): settings.del_setting(key) settings.save() diff --git a/mygpo/api/advanced/sync.py b/mygpo/api/advanced/sync.py index 51acc8592..5a82df5a8 100644 --- a/mygpo/api/advanced/sync.py +++ b/mygpo/api/advanced/sync.py @@ -14,12 +14,12 @@ @require_valid_user @check_username @never_cache -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) @cors_origin() def main(request, username): """ API Endpoint for Device Synchronisation """ - if request.method == 'GET': + if request.method == "GET": return JsonResponse(get_sync_status(request.user)) else: @@ -28,8 +28,8 @@ def main(request, username): except ValueError as e: return HttpResponseBadRequest(str(e)) - synclist = actions.get('synchronize', []) - stopsync = actions.get('stop-synchronize', []) + synclist = actions.get("synchronize", []) + stopsync = actions.get("stop-synchronize", []) try: update_sync_status(request.user, synclist, stopsync) @@ -38,7 +38,7 @@ def main(request, username): except Client.DoesNotExist as e: return HttpResponseNotFound(str(e)) - return JsonResponse(get_sync_status(user)) + return JsonResponse(get_sync_status(request.user)) def get_sync_status(user): @@ -57,31 +57,31 @@ def get_sync_status(user): else: unsynced = uids - return {'synchronized': sync_groups, 'not-synchronized': unsynced} + return {"synchronized": sync_groups, "not-synchronized": unsynced} def update_sync_status(user, synclist, stopsync): - """ Updates the current Device Sync status + """Updates the current Device Sync status Synchronisation between devices can be set up and stopped. Devices are identified by their UIDs. Unknown UIDs cause errors, no new devices are - created. """ + created.""" for devlist in synclist: if len(devlist) <= 1: - raise ValueError('at least two devices are needed to sync') + raise ValueError("at least two devices are needed to sync") # Setup all devices to sync with the first in the list uid = devlist[0] dev = user.client_set.get(uid=uid) for other_uid in devlist[1:]: - other = user.get_device_by_uid(other_uid) + other = user.client_set.get(uid=other_uid) dev.sync_with(other) for uid in stopsync: - dev = user.get_device_by_uid(uid) + dev = user.client_set.get(uid=uid) try: dev.stop_sync() except ValueError: diff --git a/mygpo/api/advanced/updates.py b/mygpo/api/advanced/updates.py index 006127e39..a7664eb5e 100644 --- a/mygpo/api/advanced/updates.py +++ b/mygpo/api/advanced/updates.py @@ -22,7 +22,7 @@ from collections import namedtuple -EpisodeStatus = namedtuple('EpisodeStatus', 'episode status action') +EpisodeStatus = namedtuple("EpisodeStatus", "episode status action") import logging @@ -30,9 +30,9 @@ class DeviceUpdates(View): - """ returns various updates for a device + """returns various updates for a device - https://gpoddernet.readthedocs.io/en/latest/api//Devices#Get_Updates """ + https://gpoddernet.readthedocs.io/en/latest/api//Devices#Get_Updates""" @method_decorator(csrf_exempt) @method_decorator(require_valid_user) @@ -56,7 +56,7 @@ def get(self, request, username, device_uid): except ValueError as e: return HttpResponseBadRequest(str(e)) - include_actions = parse_bool(request.GET.get('include_actions', False)) + include_actions = parse_bool(request.GET.get("include_actions", False)) domain = RequestSite(request).domain @@ -69,10 +69,10 @@ def get(self, request, username, device_uid): return JsonResponse( { - 'add': add, - 'rem': rem, - 'updates': updates, - 'timestamp': get_timestamp(now), + "add": add, + "rem": rem, + "updates": updates, + "timestamp": get_timestamp(now), } ) @@ -110,14 +110,14 @@ def get_episode_updates(self, user, subscribed_podcasts, since, max_per_podcast= episodes = [] for podcast in subscribed_podcasts: eps = Episode.objects.filter(podcast=podcast, released__gt=since).order_by( - '-order', '-released' + "-order", "-released" ) episodes.extend(eps[:max_per_podcast]) states = EpisodeState.dict_for_user(user, episodes) for episode in episodes: - yield EpisodeStatus(episode, states.get(episode.id, 'new'), None) + yield EpisodeStatus(episode, states.get(episode.id, "new"), None) def get_episode_data( self, episode_status, podcasts, domain, include_actions, user, devices @@ -128,20 +128,20 @@ def get_episode_data( podcast_id = episode_status.episode.podcast podcast = podcasts.get(podcast_id, None) t = episode_data(episode_status.episode, domain, podcast) - t['status'] = episode_status.status + t["status"] = episode_status.status # include latest action (bug 1419) # TODO if include_actions and episode_status.action: - t['action'] = episode_action_json(episode_status.action, user) + t["action"] = episode_action_json(episode_status.action, user) return t def get_since(self, request): """ parses the "since" parameter """ - since_ = request.GET.get('since', None) + since_ = request.GET.get("since", None) if since_ is None: - raise ValueError('parameter since missing') + raise ValueError("parameter since missing") try: return datetime.fromtimestamp(float(since_)) except ValueError as e: diff --git a/mygpo/api/backend.py b/mygpo/api/backend.py index b1037dd30..a7620ac3b 100644 --- a/mygpo/api/backend.py +++ b/mygpo/api/backend.py @@ -34,11 +34,11 @@ def get_device(user, uid, user_agent, undelete=True): if client.deleted and undelete: client.deleted = False - update_fields.append('deleted') + update_fields.append("deleted") if store_ua and user_agent and client.user_agent != user_agent: client.user_agent = user_agent - update_fields.append('user_agent') + update_fields.append("user_agent") if update_fields: client.save(update_fields=update_fields) diff --git a/mygpo/api/basic_auth.py b/mygpo/api/basic_auth.py index 9c7d05384..e24da6864 100644 --- a/mygpo/api/basic_auth.py +++ b/mygpo/api/basic_auth.py @@ -28,7 +28,7 @@ def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs): # the AUTHORIZATION header is used when passing auth-headers # from Aapache to fcgi auth = None - for h in ('AUTHORIZATION', 'HTTP_AUTHORIZATION'): + for h in ("AUTHORIZATION", "HTTP_AUTHORIZATION"): auth = request.META.get(h, auth) if not auth: @@ -40,15 +40,15 @@ def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs): auth_type, credentials = auth # NOTE: We are only support basic authentication for now. - if auth_type.lower() == 'basic': + if auth_type.lower() == "basic": try: credentials = ( - base64.b64decode(credentials).decode('utf-8').split(':', 1) + base64.b64decode(credentials).decode("utf-8").split(":", 1) ) except (UnicodeDecodeError, binascii.Error) as e: return HttpResponseBadRequest( - 'Could not decode credentials: {msg}'.format(msg=str(e)) + "Could not decode credentials: {msg}".format(msg=str(e)) ) if len(credentials) == 2: @@ -63,13 +63,13 @@ def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs): return auth_request() -def auth_request(realm=''): +def auth_request(realm=""): # Either they did not provide an authorization header or # something in the authorization attempt failed. Send a 401 # back to them to ask them to authenticate. response = HttpResponse() response.status_code = 401 - response['WWW-Authenticate'] = 'Basic realm="%s"' % realm + response["WWW-Authenticate"] = 'Basic realm="%s"' % realm return response @@ -105,7 +105,7 @@ def check_valid_user(user): return user.is_authenticated return view_or_basicauth( - protected_view, request, check_valid_user, '', *args, **kwargs + protected_view, request, check_valid_user, "", *args, **kwargs ) return wrapper @@ -126,11 +126,11 @@ def wrapper(request, username, *args, **kwargs): else: # TODO: raise SuspiciousOperation here? logger.warning( - 'username in authentication (%s) and in requested resource (%s) don\'t match' + "username in authentication (%s) and in requested resource (%s) don't match" % (request.user.username, username) ) return HttpResponseBadRequest( - 'username in authentication (%s) and in requested resource (%s) don\'t match' + "username in authentication (%s) and in requested resource (%s) don't match" % (request.user.username, username) ) diff --git a/mygpo/api/constants.py b/mygpo/api/constants.py index 03de3ec04..265095cf3 100644 --- a/mygpo/api/constants.py +++ b/mygpo/api/constants.py @@ -5,11 +5,11 @@ EPISODE_ACTION_TYPES = ( - ('download', _('downloaded')), - ('play', _('played')), - ('delete', _('deleted')), - ('new', _('marked as new')), - ('flattr', _('flattr\'d')), + ("download", _("downloaded")), + ("play", _("played")), + ("delete", _("deleted")), + ("new", _("marked as new")), + ("flattr", _("flattr'd")), ) @@ -17,6 +17,6 @@ UNSUBSCRIBE_ACTION = -1 SUBSCRIPTION_ACTION_TYPES = ( - (SUBSCRIBE_ACTION, _('subscribed')), - (UNSUBSCRIBE_ACTION, _('unsubscribed')), + (SUBSCRIBE_ACTION, _("subscribed")), + (UNSUBSCRIBE_ACTION, _("unsubscribed")), ) diff --git a/mygpo/api/httpresponse.py b/mygpo/api/httpresponse.py index 4ce92599a..9e69ca845 100644 --- a/mygpo/api/httpresponse.py +++ b/mygpo/api/httpresponse.py @@ -8,10 +8,10 @@ def __init__(self, object, jsonp_padding=None): content = json.dumps(object, ensure_ascii=True) if jsonp_padding: - content = '%(func)s(%(obj)s)' % {'func': jsonp_padding, 'obj': content} - content_type = 'application/json-p' + content = "%(func)s(%(obj)s)" % {"func": jsonp_padding, "obj": content} + content_type = "application/json-p" else: - content_type = 'application/json' + content_type = "application/json" super(JsonResponse, self).__init__(content, content_type=content_type) diff --git a/mygpo/api/legacy.py b/mygpo/api/legacy.py index b9c3b8baf..742044962 100644 --- a/mygpo/api/legacy.py +++ b/mygpo/api/legacy.py @@ -15,33 +15,33 @@ logger = logging.getLogger(__name__) -LEGACY_DEVICE_NAME = 'Legacy Device' -LEGACY_DEVICE_UID = 'legacy' +LEGACY_DEVICE_NAME = "Legacy Device" +LEGACY_DEVICE_UID = "legacy" @never_cache @csrf_exempt def upload(request): try: - emailaddr = request.POST['username'] - password = request.POST['password'] - action = request.POST['action'] - protocol = request.POST['protocol'] - opml = request.FILES['opml'].read() + emailaddr = request.POST["username"] + password = request.POST["password"] + action = request.POST["action"] + protocol = request.POST["protocol"] + opml = request.FILES["opml"].read() except MultiValueDictKeyError: - return HttpResponse("@PROTOERROR", content_type='text/plain') + return HttpResponse("@PROTOERROR", content_type="text/plain") user = auth(emailaddr, password) if not user: - return HttpResponse('@AUTHFAIL', content_type='text/plain') + return HttpResponse("@AUTHFAIL", content_type="text/plain") - dev = get_device(user, LEGACY_DEVICE_UID, request.META.get('HTTP_USER_AGENT', '')) + dev = get_device(user, LEGACY_DEVICE_UID, request.META.get("HTTP_USER_AGENT", "")) existing_urls = [x.url for x in dev.get_subscribed_podcasts()] i = Importer(opml) - podcast_urls = [p['url'] for p in i.items] + podcast_urls = [p["url"] for p in i.items] podcast_urls = map(normalize_feed_url, podcast_urls) podcast_urls = list(filter(None, podcast_urls)) @@ -54,27 +54,27 @@ def upload(request): for n in new: p = Podcast.objects.get_or_create_for_url(n).object - subscribe(p, user, dev) + subscribe(p.pk, user.pk, dev.uid) for r in rem: p = Podcast.objects.get_or_create_for_url(r).object - unsubscribe(p, user, dev) + unsubscribe(p.pk, user.pk, dev.uid) - return HttpResponse('@SUCCESS', content_type='text/plain') + return HttpResponse("@SUCCESS", content_type="text/plain") @never_cache @csrf_exempt def getlist(request): - emailaddr = request.GET.get('username', None) - password = request.GET.get('password', None) + emailaddr = request.GET.get("username", None) + password = request.GET.get("password", None) user = auth(emailaddr, password) if user is None: - return HttpResponse('@AUTHFAIL', content_type='text/plain') + return HttpResponse("@AUTHFAIL", content_type="text/plain") dev = get_device( - user, LEGACY_DEVICE_UID, request.META.get('HTTP_USER_AGENT', ''), undelete=True + user, LEGACY_DEVICE_UID, request.META.get("HTTP_USER_AGENT", ""), undelete=True ) podcasts = dev.get_subscribed_podcasts() @@ -83,7 +83,7 @@ def getlist(request): opml = exporter.generate(podcasts) - return HttpResponse(opml, content_type='text/xml') + return HttpResponse(opml, content_type="text/xml") def auth(emailaddr, password): diff --git a/mygpo/api/opml.py b/mygpo/api/opml.py index 9f62e11d1..3ba5c8151 100644 --- a/mygpo/api/opml.py +++ b/mygpo/api/opml.py @@ -14,7 +14,7 @@ class Importer(object): - VALID_TYPES = ('rss', 'link') + VALID_TYPES = ("rss", "link") def __init__(self, content): """ @@ -28,28 +28,28 @@ def __init__(self, content): except ExpatError as e: raise ValueError from e - for outline in doc.getElementsByTagName('outline'): + for outline in doc.getElementsByTagName("outline"): if ( - outline.getAttribute('type') in self.VALID_TYPES - and outline.getAttribute('xmlUrl') - or outline.getAttribute('url') + outline.getAttribute("type") in self.VALID_TYPES + and outline.getAttribute("xmlUrl") + or outline.getAttribute("url") ): channel = { - 'url': outline.getAttribute('xmlUrl') - or outline.getAttribute('url'), - 'title': outline.getAttribute('title') - or outline.getAttribute('text') - or outline.getAttribute('xmlUrl') - or outline.getAttribute('url'), - 'description': outline.getAttribute('text') - or outline.getAttribute('xmlUrl') - or outline.getAttribute('url'), + "url": outline.getAttribute("xmlUrl") + or outline.getAttribute("url"), + "title": outline.getAttribute("title") + or outline.getAttribute("text") + or outline.getAttribute("xmlUrl") + or outline.getAttribute("url"), + "description": outline.getAttribute("text") + or outline.getAttribute("xmlUrl") + or outline.getAttribute("url"), } - if channel['description'] == channel['title']: - channel['description'] = channel['url'] + if channel["description"] == channel["title"]: + channel["description"] = channel["url"] - for attr in ('url', 'title', 'description'): + for attr in ("url", "title", "description"): channel[attr] = channel[attr].strip() self.items.append(channel) @@ -61,7 +61,7 @@ class Exporter(object): 2.0 format. See www.opml.org for the OPML specification. """ - def __init__(self, title='my.gpodder.org Subscriptions'): + def __init__(self, title="my.gpodder.org Subscriptions"): self.title = title self.created = email.utils.formatdate(localtime=True) @@ -74,8 +74,8 @@ def generate(self, channels): """ doc = xml.dom.minidom.Document() - opml = doc.createElement('opml') - opml.setAttribute('version', '2.0') + opml = doc.createElement("opml") + opml.setAttribute("version", "2.0") doc.appendChild(opml) def create_node(name, content): @@ -83,41 +83,41 @@ def create_node(name, content): node.appendChild(doc.createTextNode(content)) return node - head = doc.createElement('head') - head.appendChild(create_node('title', self.title or '')) - head.appendChild(create_node('dateCreated', self.created)) + head = doc.createElement("head") + head.appendChild(create_node("title", self.title or "")) + head.appendChild(create_node("dateCreated", self.created)) opml.appendChild(head) def create_outline(channel): from mygpo.subscriptions.models import SubscribedPodcast from mygpo.podcasts.models import PodcastGroup - outline = doc.createElement('outline') + outline = doc.createElement("outline") if isinstance(channel, SubscribedPodcast): title = channel.podcast.title - outline.setAttribute('xmlUrl', channel.ref_url) - outline.setAttribute('description', channel.podcast.description or '') - outline.setAttribute('type', 'rss') - outline.setAttribute('htmlUrl', channel.podcast.link or '') + outline.setAttribute("xmlUrl", channel.ref_url) + outline.setAttribute("description", channel.podcast.description or "") + outline.setAttribute("type", "rss") + outline.setAttribute("htmlUrl", channel.podcast.link or "") elif isinstance(channel, PodcastGroup): title = channel.title for subchannel in channel.podcast_set.all(): outline.appendChild(create_outline(subchannel)) else: title = channel.title - outline.setAttribute('xmlUrl', channel.url) - outline.setAttribute('description', channel.description or '') - outline.setAttribute('type', 'rss') - outline.setAttribute('htmlUrl', channel.link or '') + outline.setAttribute("xmlUrl", channel.url) + outline.setAttribute("description", channel.description or "") + outline.setAttribute("type", "rss") + outline.setAttribute("htmlUrl", channel.link or "") - outline.setAttribute('title', title or '') - outline.setAttribute('text', title or '') + outline.setAttribute("title", title or "") + outline.setAttribute("text", title or "") return outline - body = doc.createElement('body') + body = doc.createElement("body") for channel in channels: body.appendChild(create_outline(channel)) opml.appendChild(body) - return doc.toprettyxml(encoding='utf-8', indent=' ', newl=os.linesep) + return doc.toprettyxml(encoding="utf-8", indent=" ", newl=os.linesep) diff --git a/mygpo/api/simple.py b/mygpo/api/simple.py index dbd265901..88eafde47 100644 --- a/mygpo/api/simple.py +++ b/mygpo/api/simple.py @@ -29,14 +29,14 @@ logger = logging.getLogger(__name__) -ALLOWED_FORMATS = ('txt', 'opml', 'json', 'jsonp', 'xml') +ALLOWED_FORMATS = ("txt", "opml", "json", "jsonp", "xml") def check_format(fn): @wraps(fn) def tmp(request, format, *args, **kwargs): if format not in ALLOWED_FORMATS: - return HttpResponseBadRequest('Invalid format') + return HttpResponseBadRequest("Invalid format") return fn(request, *args, format=format, **kwargs) @@ -48,26 +48,26 @@ def tmp(request, format, *args, **kwargs): @check_username @check_format @never_cache -@allowed_methods(['GET', 'PUT', 'POST']) +@allowed_methods(["GET", "PUT", "POST"]) @cors_origin() def subscriptions(request, username, device_uid, format): - user_agent = request.META.get('HTTP_USER_AGENT', '') + user_agent = request.META.get("HTTP_USER_AGENT", "") - if request.method == 'GET': - title = _('%(username)s\'s Subscription List') % {'username': username} + if request.method == "GET": + title = _("%(username)s's Subscription List") % {"username": username} subscriptions = get_subscriptions(request.user, device_uid, user_agent) return format_podcast_list( - subscriptions, format, title, jsonp_padding=request.GET.get('jsonp') + subscriptions, format, title, jsonp_padding=request.GET.get("jsonp") ) - elif request.method in ('PUT', 'POST'): + elif request.method in ("PUT", "POST"): try: - body = request.body.decode('utf-8') + body = request.body.decode("utf-8") subscriptions = parse_subscription(body, format) except ValueError as e: - return HttpResponseBadRequest('Unable to parse POST data: %s' % str(e)) + return HttpResponseBadRequest("Unable to parse POST data: %s" % str(e)) return set_subscriptions(subscriptions, request.user, device_uid, user_agent) @@ -77,20 +77,20 @@ def subscriptions(request, username, device_uid, format): @check_username @check_format @never_cache -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cors_origin() def all_subscriptions(request, username, format): try: - scale = int(request.GET.get('scale_logo', 64)) + scale = int(request.GET.get("scale_logo", 64)) except (TypeError, ValueError): - return HttpResponseBadRequest('scale_logo has to be a numeric value') + return HttpResponseBadRequest("scale_logo has to be a numeric value") if scale not in range(1, 257): - return HttpResponseBadRequest('scale_logo has to be a number from 1 to 256') + return HttpResponseBadRequest("scale_logo has to be a number from 1 to 256") subscriptions = get_subscribed_podcasts(request.user) - title = _('%(username)s\'s Subscription List') % {'username': username} + title = _("%(username)s's Subscription List") % {"username": username} domain = RequestSite(request).domain p_data = lambda p: podcast_data(p, domain, scale) return format_podcast_list( @@ -98,7 +98,7 @@ def all_subscriptions(request, username, format): format, title, json_map=p_data, - xml_template='podcasts.xml', + xml_template="podcasts.xml", request=request, ) @@ -131,47 +131,47 @@ def default_get_podcast(p): get_podcast = get_podcast or default_get_podcast - if format == 'txt': + if format == "txt": podcasts = map(get_podcast, obj_list) - s = '\n'.join([p.url for p in podcasts] + ['']) - return HttpResponse(s, content_type='text/plain') + s = "\n".join([p.url for p in podcasts] + [""]) + return HttpResponse(s, content_type="text/plain") - elif format == 'opml': + elif format == "opml": podcasts = map(get_podcast, obj_list) exporter = Exporter(title) opml = exporter.generate(podcasts) - return HttpResponse(opml, content_type='text/xml') + return HttpResponse(opml, content_type="text/xml") - elif format == 'json': + elif format == "json": objs = list(map(json_map, obj_list)) return JsonResponse(objs) - elif format == 'jsonp': - ALLOWED_FUNCNAME = string.ascii_letters + string.digits + '_' + elif format == "jsonp": + ALLOWED_FUNCNAME = string.ascii_letters + string.digits + "_" if not jsonp_padding: return HttpResponseBadRequest( - 'For a JSONP response, specify the name of the callback function in the jsonp parameter' + "For a JSONP response, specify the name of the callback function in the jsonp parameter" ) if any(x not in ALLOWED_FUNCNAME for x in jsonp_padding): return HttpResponseBadRequest( - 'JSONP padding can only contain the characters %(char)s' - % {'char': ALLOWED_FUNCNAME} + "JSONP padding can only contain the characters %(char)s" + % {"char": ALLOWED_FUNCNAME} ) objs = list(map(json_map, obj_list)) return JsonResponse(objs, jsonp_padding=jsonp_padding) - elif format == 'xml': + elif format == "xml": if None in (xml_template, request): - return HttpResponseBadRequest('XML is not a valid format for this request') + return HttpResponseBadRequest("XML is not a valid format for this request") podcasts = map(json_map, obj_list) - template_args.update({'podcasts': podcasts}) + template_args.update({"podcasts": podcasts}) return render( - request, xml_template, template_args, content_type='application/xml' + request, xml_template, template_args, content_type="application/xml" ) else: @@ -185,22 +185,22 @@ def get_subscriptions(user, device_uid, user_agent=None): def parse_subscription(raw_post_data, format): """ Parses the data according to the format """ - if format == 'txt': - urls = raw_post_data.split('\n') + if format == "txt": + urls = raw_post_data.split("\n") - elif format == 'opml': - begin = raw_post_data.find('') + 7 + elif format == "opml": + begin = raw_post_data.find("") + 7 i = Importer(content=raw_post_data[begin:end]) - urls = [p['url'] for p in i.items] + urls = [p["url"] for p in i.items] - elif format == 'json': - begin = raw_post_data.find('[') - end = raw_post_data.find(']') + 1 + elif format == "json": + begin = raw_post_data.find("[") + end = raw_post_data.find("]") + 1 urls = json.loads(raw_post_data[begin:end]) if not isinstance(urls, list): - raise ValueError('A list of feed URLs was expected') + raise ValueError("A list of feed URLs was expected") else: return [] @@ -223,18 +223,18 @@ def set_subscriptions(urls, user, device_uid, user_agent): remove_podcasts = Podcast.objects.filter(urls__url__in=rem) for podcast in remove_podcasts: - unsubscribe(podcast, user, device) + unsubscribe(podcast.pk, user.pk, device.uid) for url in new: podcast = Podcast.objects.get_or_create_for_url(url).object - subscribe(podcast, user, device, url) + subscribe(podcast.pk, user.pk, device.uid, url) # Only an empty response is a successful response - return HttpResponse('', content_type='text/plain') + return HttpResponse("", content_type="text/plain") @check_format -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cache_page(60 * 60) @cors_origin() def toplist(request, count, format): @@ -244,55 +244,55 @@ def toplist(request, count, format): domain = RequestSite(request).domain try: - scale = int(request.GET.get('scale_logo', 64)) + scale = int(request.GET.get("scale_logo", 64)) except (TypeError, ValueError): - return HttpResponseBadRequest('scale_logo has to be a numeric value') + return HttpResponseBadRequest("scale_logo has to be a numeric value") if scale not in range(1, 257): - return HttpResponseBadRequest('scale_logo has to be a number from 1 to 256') + return HttpResponseBadRequest("scale_logo has to be a number from 1 to 256") def json_map(t): podcast = t p = podcast_data(podcast, domain, scale) return p - title = _('gpodder.net - Top %(count)d') % {'count': len(entries)} + title = _("gpodder.net - Top %(count)d") % {"count": len(entries)} return format_podcast_list( entries, format, title, get_podcast=lambda t: t, json_map=json_map, - jsonp_padding=request.GET.get('jsonp', ''), - xml_template='podcasts.xml', + jsonp_padding=request.GET.get("jsonp", ""), + xml_template="podcasts.xml", request=request, ) @check_format @cache_page(60 * 60) -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cors_origin() def search(request, format): NUM_RESULTS = 20 - query = request.GET.get('q', '') + query = request.GET.get("q", "") try: - scale = int(request.GET.get('scale_logo', 64)) + scale = int(request.GET.get("scale_logo", 64)) except (TypeError, ValueError): - return HttpResponseBadRequest('scale_logo has to be a numeric value') + return HttpResponseBadRequest("scale_logo has to be a numeric value") if scale not in range(1, 257): - return HttpResponseBadRequest('scale_logo has to be a number from 1 to 256') + return HttpResponseBadRequest("scale_logo has to be a number from 1 to 256") if not query: - return HttpResponseBadRequest('/search.opml|txt|json?q={query}') + return HttpResponseBadRequest("/search.opml|txt|json?q={query}") results = search_podcasts(query)[:NUM_RESULTS] - title = _('gpodder.net - Search') + title = _("gpodder.net - Search") domain = RequestSite(request).domain p_data = lambda p: podcast_data(p, domain, scale) return format_podcast_list( @@ -300,8 +300,8 @@ def search(request, format): format, title, json_map=p_data, - jsonp_padding=request.GET.get('jsonp', ''), - xml_template='podcasts.xml', + jsonp_padding=request.GET.get("jsonp", ""), + xml_template="podcasts.xml", request=request, ) @@ -309,7 +309,7 @@ def search(request, format): @require_valid_user @check_format @never_cache -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cors_origin() def suggestions(request, count, format): count = parse_range(count, 1, 100, 100) @@ -318,7 +318,7 @@ def suggestions(request, count, format): suggestions = Podcast.objects.filter( podcastsuggestion__suggested_to=user, podcastsuggestion__deleted=False ) - title = _('gpodder.net - %(count)d Suggestions') % {'count': len(suggestions)} + title = _("gpodder.net - %(count)d Suggestions") % {"count": len(suggestions)} domain = RequestSite(request).domain p_data = lambda p: podcast_data(p, domain) return format_podcast_list( @@ -326,35 +326,35 @@ def suggestions(request, count, format): format, title, json_map=p_data, - jsonp_padding=request.GET.get('jsonp'), + jsonp_padding=request.GET.get("jsonp"), ) @check_format -@allowed_methods(['GET']) +@allowed_methods(["GET"]) @cache_page(60 * 60) @cors_origin() def example_podcasts(request, format): - podcasts = cache.get('example-podcasts', None) + podcasts = cache.get("example-podcasts", None) try: - scale = int(request.GET.get('scale_logo', 64)) + scale = int(request.GET.get("scale_logo", 64)) except (TypeError, ValueError): - return HttpResponseBadRequest('scale_logo has to be a numeric value') + return HttpResponseBadRequest("scale_logo has to be a numeric value") if scale not in range(1, 257): - return HttpResponseBadRequest('scale_logo has to be a number from 1 to 256') + return HttpResponseBadRequest("scale_logo has to be a number from 1 to 256") if not podcasts: podcasts = list(ExamplePodcast.objects.get_podcasts()) - cache.set('example-podcasts', podcasts) + cache.set("example-podcasts", podcasts) podcast_ad = Podcast.objects.get_advertised_podcast() if podcast_ad: podcasts = [podcast_ad] + podcasts - title = 'gPodder Podcast Directory' + title = "gPodder Podcast Directory" domain = RequestSite(request).domain p_data = lambda p: podcast_data(p, domain, scale) return format_podcast_list( @@ -362,6 +362,6 @@ def example_podcasts(request, format): format, title, json_map=p_data, - xml_template='podcasts.xml', + xml_template="podcasts.xml", request=request, ) diff --git a/mygpo/api/subscriptions.py b/mygpo/api/subscriptions.py index 89750f635..d1223e6e2 100644 --- a/mygpo/api/subscriptions.py +++ b/mygpo/api/subscriptions.py @@ -27,28 +27,28 @@ def get(self, request, version, username, device_uid): device = get_object_or_404(Client, user=user, uid=device_uid) since = self.get_since(request) add, rem, until = self.get_changes(user, device, since, now) - return JsonResponse({'add': add, 'remove': rem, 'timestamp': until}) + return JsonResponse({"add": add, "remove": rem, "timestamp": until}) def post(self, request, version, username, device_uid): """ Client sends subscription updates """ now = get_timestamp(datetime.utcnow()) logger.info( - 'Subscription Upload @{username}/{device_uid}'.format( + "Subscription Upload @{username}/{device_uid}".format( username=request.user.username, device_uid=device_uid ) ) d = get_device( - request.user, device_uid, request.META.get('HTTP_USER_AGENT', '') + request.user, device_uid, request.META.get("HTTP_USER_AGENT", "") ) actions = self.parsed_body(request) - add = list(filter(None, actions.get('add', []))) - rem = list(filter(None, actions.get('remove', []))) + add = list(filter(None, actions.get("add", []))) + rem = list(filter(None, actions.get("remove", []))) logger.info( - 'Subscription Upload @{username}/{device_uid}: add ' - '{num_add}, remove {num_remove}'.format( + "Subscription Upload @{username}/{device_uid}: add " + "{num_add}, remove {num_remove}".format( username=request.user.username, device_uid=device_uid, num_add=len(add), @@ -58,7 +58,7 @@ def post(self, request, version, username, device_uid): update_urls = self.update_subscriptions(request.user, d, add, rem) - return JsonResponse({'timestamp': now, 'update_urls': update_urls}) + return JsonResponse({"timestamp": now, "update_urls": update_urls}) def update_subscriptions(self, user, device, add, remove): @@ -85,22 +85,22 @@ def update_subscriptions(self, user, device, add, remove): for add_url in add_s: podcast = Podcast.objects.get_or_create_for_url(add_url).object - subscribe(podcast, user, device, add_url) + subscribe(podcast.pk, user.pk, device.uid, add_url) remove_podcasts = Podcast.objects.filter(urls__url__in=rem_s) for podcast in remove_podcasts: - unsubscribe(podcast, user, device) + unsubscribe(podcast.pk, user.pk, device.uid) return updated_urls def get_changes(self, user, device, since, until): """ Returns subscription changes for the given device """ history = get_subscription_history(user, device, since, until) - logger.info('Subscription History: {num}'.format(num=len(history))) + logger.info("Subscription History: {num}".format(num=len(history))) add, rem = subscription_diff(history) logger.info( - 'Subscription Diff: +{num_add}/-{num_remove}'.format( + "Subscription Diff: +{num_add}/-{num_remove}".format( num_add=len(add), num_remove=len(rem) ) ) diff --git a/mygpo/api/tasks.py b/mygpo/api/tasks.py index a61df8537..a2207e949 100644 --- a/mygpo/api/tasks.py +++ b/mygpo/api/tasks.py @@ -11,7 +11,7 @@ @celery.task(max_retries=5, default_retry_delay=60) @close_connection def import_episode_actions(user, actions, upload_ts, ua_string): - logger.info('Importing %d tasks for user %s', len(actions), user) + logger.info("Importing %d tasks for user %s", len(actions), user) update_episodes(user, actions, upload_ts, ua_string) diff --git a/mygpo/api/templates/podcasts.xml b/mygpo/api/templates/podcasts.xml index adba9e422..1fcfa68ca 100644 --- a/mygpo/api/templates/podcasts.xml +++ b/mygpo/api/templates/podcasts.xml @@ -8,7 +8,6 @@ {{ podcast.author|default:"" }} {{ podcast.description|default:"" }} {{ podcast.subscribers }} - {{ podcast.subscribers_last_week }} {{ podcast.logo_url|default:"" }} {{ podcast.scaled_logo_url }} diff --git a/mygpo/api/tests.py b/mygpo/api/tests.py index 001943f20..66accfb75 100644 --- a/mygpo/api/tests.py +++ b/mygpo/api/tests.py @@ -27,16 +27,16 @@ class AdvancedAPITests(unittest.TestCase): def setUp(self): User = get_user_model() - self.password = 'asdf' - self.username = 'adv-api-user' - self.user = User(username=self.username, email='user@example.com') + self.password = "asdf" + self.username = "adv-api-user" + self.user = User(username=self.username, email="user@example.com") self.user.set_password(self.password) self.user.save() self.user.is_active = True self.client = Client() self.extra = { - 'HTTP_AUTHORIZATION': create_auth_string(self.username, self.password) + "HTTP_AUTHORIZATION": create_auth_string(self.username, self.password) } self.action_data = [ @@ -64,24 +64,24 @@ def test_episode_actions(self): response = self._upload_episode_actions(self.user, self.action_data, self.extra) self.assertEqual(response.status_code, 200, response.content) - url = reverse(episodes, kwargs={'version': '2', 'username': self.user.username}) - response = self.client.get(url, {'since': '0'}, **self.extra) + url = reverse(episodes, kwargs={"version": "2", "username": self.user.username}) + response = self.client.get(url, {"since": "0"}, **self.extra) self.assertEqual(response.status_code, 200, response.content) - response_obj = json.loads(response.content.decode('utf-8')) - actions = response_obj['actions'] + response_obj = json.loads(response.content.decode("utf-8")) + actions = response_obj["actions"] self.assertTrue(self.compare_action_list(self.action_data, actions)) def test_invalid_client_id(self): """ Invalid Client ID should return 400 """ action_data = copy.deepcopy(self.action_data) - action_data[0]['device'] = "gpodder@abcdef123" + action_data[0]["device"] = "gpodder@abcdef123" response = self._upload_episode_actions(self.user, action_data, self.extra) self.assertEqual(response.status_code, 400, response.content) def _upload_episode_actions(self, user, action_data, extra): - url = reverse(episodes, kwargs={'version': '2', 'username': self.user.username}) + url = reverse(episodes, kwargs={"version": "2", "username": self.user.username}) return self.client.post( url, json.dumps(action_data), content_type="application/json", **extra ) @@ -94,7 +94,7 @@ def compare_action_list(self, as1, as2): found = True if not found: - raise ValueError('%s not found in %s' % (a1, as2)) + raise ValueError("%s not found in %s" % (a1, as2)) return False return True @@ -111,27 +111,27 @@ class SubscriptionAPITests(unittest.TestCase): def setUp(self): User = get_user_model() - self.password = 'asdf' - self.username = 'subscription-api-user' - self.device_uid = 'test-device' - self.user = User(username=self.username, email='user@example.com') + self.password = "asdf" + self.username = "subscription-api-user" + self.device_uid = "test-device" + self.user = User(username=self.username, email="user@example.com") self.user.set_password(self.password) self.user.save() self.user.is_active = True self.client = Client() self.extra = { - 'HTTP_AUTHORIZATION': create_auth_string(self.username, self.password) + "HTTP_AUTHORIZATION": create_auth_string(self.username, self.password) } - self.action_data = {'add': ['http://example.com/podcast.rss']} + self.action_data = {"add": ["http://example.com/podcast.rss"]} self.url = reverse( - 'subscriptions-api', + "subscriptions-api", kwargs={ - 'version': '2', - 'username': self.user.username, - 'device_uid': self.device_uid, + "version": "2", + "username": self.user.username, + "device_uid": self.device_uid, }, ) @@ -151,15 +151,15 @@ def test_set_get_subscriptions(self): self.assertEqual(response.status_code, 200, response.content) # verify that the subscription is returned correctly - response = self.client.get(self.url, {'since': '0'}, **self.extra) + response = self.client.get(self.url, {"since": "0"}, **self.extra) self.assertEqual(response.status_code, 200, response.content) - response_obj = json.loads(response.content.decode('utf-8')) - self.assertEqual(self.action_data['add'], response_obj['add']) - self.assertEqual([], response_obj.get('remove', [])) + response_obj = json.loads(response.content.decode("utf-8")) + self.assertEqual(self.action_data["add"], response_obj["add"]) + self.assertEqual([], response_obj.get("remove", [])) def test_unauth_request(self): """ Tests that an unauthenticated request gives a 401 response """ - response = self.client.get(self.url, {'since': '0'}) + response = self.client.get(self.url, {"since": "0"}) self.assertEqual(response.status_code, 401, response.content) @@ -168,21 +168,21 @@ class DirectoryTest(TestCase): def setUp(self): self.podcast = Podcast.objects.get_or_create_for_url( - 'http://example.com/directory-podcast.xml', defaults={'title': 'My Podcast'} + "http://example.com/directory-podcast.xml", defaults={"title": "My Podcast"} ).object self.episode = Episode.objects.get_or_create_for_url( self.podcast, - 'http://example.com/directory-podcast/1.mp3', - defaults={'title': 'My Episode'}, + "http://example.com/directory-podcast/1.mp3", + defaults={"title": "My Episode"}, ).object self.client = Client() def test_episode_info(self): """ Test that the expected number of queries is executed """ url = ( - reverse('api-episode-info') - + '?' - + urlencode((('podcast', self.podcast.url), ('url', self.episode.url))) + reverse("api-episode-info") + + "?" + + urlencode((("podcast", self.podcast.url), ("url", self.episode.url))) ) resp = self.client.get(url) @@ -193,23 +193,23 @@ def test_episode_info(self): class EpisodeActionTests(TestCase): def setUp(self): self.podcast = Podcast.objects.get_or_create_for_url( - 'http://example.com/directory-podcast.xml', defaults={'title': 'My Podcast'} + "http://example.com/directory-podcast.xml", defaults={"title": "My Podcast"} ).object self.episode = Episode.objects.get_or_create_for_url( self.podcast, - 'http://example.com/directory-podcast/1.mp3', - defaults={'title': 'My Episode'}, + "http://example.com/directory-podcast/1.mp3", + defaults={"title": "My Episode"}, ).object User = get_user_model() - self.password = 'asdf' - self.username = 'adv-api-user' - self.user = User(username=self.username, email='user@example.com') + self.password = "asdf" + self.username = "adv-api-user" + self.user = User(username=self.username, email="user@example.com") self.user.set_password(self.password) self.user.save() self.user.is_active = True self.client = Client() self.extra = { - 'HTTP_AUTHORIZATION': create_auth_string(self.username, self.password) + "HTTP_AUTHORIZATION": create_auth_string(self.username, self.password) } def tearDown(self): @@ -233,11 +233,11 @@ def test_limit_actions(self): ) timestamps.append(timestamp) - url = reverse(episodes, kwargs={'version': '2', 'username': self.user.username}) - response = self.client.get(url, {'since': '0'}, **self.extra) + url = reverse(episodes, kwargs={"version": "2", "username": self.user.username}) + response = self.client.get(url, {"since": "0"}, **self.extra) self.assertEqual(response.status_code, 200, response.content) - response_obj = json.loads(response.content.decode('utf-8')) - actions = response_obj['actions'] + response_obj = json.loads(response.content.decode("utf-8")) + actions = response_obj["actions"] # 10 actions should be returned self.assertEqual(len(actions), 10) @@ -246,27 +246,27 @@ def test_limit_actions(self): # the first 10 actions, according to their timestamp should be returned for action, timestamp in zip(actions, timestamps): - self.assertEqual(timestamp.isoformat(), action['timestamp']) + self.assertEqual(timestamp.isoformat(), action["timestamp"]) # the `timestamp` field in the response should be the timestamp of the # last returned action - self.assertEqual(get_timestamp(timestamps[9]), response_obj['timestamp']) + self.assertEqual(get_timestamp(timestamps[9]), response_obj["timestamp"]) def test_no_actions(self): """ Test when there are no actions to return """ t1 = get_timestamp(datetime.utcnow()) - url = reverse(episodes, kwargs={'version': '2', 'username': self.user.username}) - response = self.client.get(url, {'since': '0'}, **self.extra) + url = reverse(episodes, kwargs={"version": "2", "username": self.user.username}) + response = self.client.get(url, {"since": "0"}, **self.extra) self.assertEqual(response.status_code, 200, response.content) - response_obj = json.loads(response.content.decode('utf-8')) - actions = response_obj['actions'] + response_obj = json.loads(response.content.decode("utf-8")) + actions = response_obj["actions"] # 10 actions should be returned self.assertEqual(len(actions), 0) - returned = response_obj['timestamp'] + returned = response_obj["timestamp"] t2 = get_timestamp(datetime.utcnow()) # the `timestamp` field in the response should be the timestamp of the # last returned action @@ -277,29 +277,29 @@ def test_no_actions(self): class SimpleAPITests(unittest.TestCase): def setUp(self): User = get_user_model() - self.password = 'asdf' - self.username = 'subscription-api-user' - self.device_uid = 'test-device' - self.user = User(username=self.username, email='user@example.com') + self.password = "asdf" + self.username = "subscription-api-user" + self.device_uid = "test-device" + self.user = User(username=self.username, email="user@example.com") self.user.set_password(self.password) self.user.save() self.user.is_active = True self.client = Client() self.extra = { - 'HTTP_AUTHORIZATION': create_auth_string(self.username, self.password) + "HTTP_AUTHORIZATION": create_auth_string(self.username, self.password) } - self.formats = ['txt', 'json', 'jsonp', 'opml'] + self.formats = ["txt", "json", "jsonp", "opml"] self.subscriptions_urls = dict( (fmt, self.get_subscriptions_url(fmt)) for fmt in self.formats ) self.blank_values = { - 'txt': b'\n', - 'json': b'[]', - 'opml': Exporter('Subscriptions').generate([]), + "txt": b"\n", + "json": b"[]", + "opml": Exporter("Subscriptions").generate([]), } self.all_subscriptions_url = reverse( - 'api-all-subscriptions', - kwargs={'format': 'txt', 'username': self.user.username}, + "api-all-subscriptions", + kwargs={"format": "txt", "username": self.user.username}, ) self.toplist_urls = dict( (fmt, self.get_toplist_url(fmt)) for fmt in self.formats @@ -310,20 +310,20 @@ def tearDown(self): self.user.delete() def get_toplist_url(self, fmt): - return reverse('api-simple-toplist-50', kwargs={'format': fmt}) + return reverse("api-simple-toplist-50", kwargs={"format": fmt}) def get_subscriptions_url(self, fmt): return reverse( - 'api-simple-subscriptions', + "api-simple-subscriptions", kwargs={ - 'format': fmt, - 'username': self.user.username, - 'device_uid': self.device_uid, + "format": fmt, + "username": self.user.username, + "device_uid": self.device_uid, }, ) def get_search_url(self, fmt): - return reverse('api-simple-search', kwargs={'format': fmt}) + return reverse("api-simple-search", kwargs={"format": fmt}) def _test_response_for_data(self, url, data, status_code, content): response = self.client.get(url, data) @@ -332,73 +332,73 @@ def _test_response_for_data(self, url, data, status_code, content): def test_get_subscriptions_empty(self): testers = { - 'txt': lambda c: self.assertEqual(c, b''), - 'json': lambda c: self.assertEqual(c, b'[]'), - 'jsonp': lambda c: self.assertEqual(c, b'test([])'), - 'opml': lambda c: self.assertListEqual(Importer(c).items, []), + "txt": lambda c: self.assertEqual(c, b""), + "json": lambda c: self.assertEqual(c, b"[]"), + "jsonp": lambda c: self.assertEqual(c, b"test([])"), + "opml": lambda c: self.assertListEqual(Importer(c).items, []), } for fmt in self.formats: url = self.subscriptions_urls[fmt] - response = self.client.get(url, data={'jsonp': 'test'}, **self.extra) + response = self.client.get(url, data={"jsonp": "test"}, **self.extra) self.assertEqual(response.status_code, 200, response.content) testers[fmt](response.content) def test_get_subscriptions_invalid_jsonp(self): - url = self.subscriptions_urls['jsonp'] - response = self.client.get(url, data={'jsonp': '!'}, **self.extra) + url = self.subscriptions_urls["jsonp"] + response = self.client.get(url, data={"jsonp": "!"}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_subscriptions_with_content(self): - sample_url = 'http://example.com/directory-podcast.xml' + sample_url = "http://example.com/directory-podcast.xml" podcast = Podcast.objects.get_or_create_for_url( - sample_url, defaults={'title': 'My Podcast'} + sample_url, defaults={"title": "My Podcast"} ).object with unittest.mock.patch( - 'mygpo.users.models.Client.get_subscribed_podcasts' + "mygpo.users.models.Client.get_subscribed_podcasts" ) as mock_get: mock_get.return_value = [podcast] - response = self.client.get(self.subscriptions_urls['txt'], **self.extra) + response = self.client.get(self.subscriptions_urls["txt"], **self.extra) self.assertEqual(response.status_code, 200, response.content) - retrieved_urls = response.content.split(b'\n')[:-1] + retrieved_urls = response.content.split(b"\n")[:-1] expected_urls = [sample_url.encode()] self.assertEqual(retrieved_urls, expected_urls) def test_post_subscription_valid(self): - sample_url = 'http://example.com/directory-podcast.xml' + sample_url = "http://example.com/directory-podcast.xml" podcast = Podcast.objects.get_or_create_for_url( - sample_url, defaults={'title': 'My Podcast'} + sample_url, defaults={"title": "My Podcast"} ).object payloads = { - 'txt': sample_url, - 'json': json.dumps([sample_url]), + "txt": sample_url, + "json": json.dumps([sample_url]), #'opml': Exporter('Subscriptions').generate([sample_url]), - 'opml': Exporter('Subscriptions').generate([podcast]), + "opml": Exporter("Subscriptions").generate([podcast]), } payloads = dict( - (fmt, format_podcast_list([podcast], fmt, 'test title').content) + (fmt, format_podcast_list([podcast], fmt, "test title").content) for fmt in self.formats ) for fmt in self.formats: url = self.subscriptions_urls[fmt] payload = payloads[fmt] - response = self.client.generic('POST', url, payload, **self.extra) + response = self.client.generic("POST", url, payload, **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_post_subscription_invalid(self): - url = self.subscriptions_urls['json'] - payload = 'invalid_json' - response = self.client.generic('POST', url, payload, **self.extra) + url = self.subscriptions_urls["json"] + payload = "invalid_json" + response = self.client.generic("POST", url, payload, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_invalid_scale(self): response = self.client.get( - self.all_subscriptions_url, data={'scale_logo': 0}, **self.extra + self.all_subscriptions_url, data={"scale_logo": 0}, **self.extra ) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_non_numeric_scale(self): response = self.client.get( - self.all_subscriptions_url, data={'scale_logo': 'a'}, **self.extra + self.all_subscriptions_url, data={"scale_logo": "a"}, **self.extra ) self.assertEqual(response.status_code, 400, response.content) @@ -408,52 +408,52 @@ def test_get_all_subscriptions_valid_empty(self): def test_get_toplist_invalid_scale(self): response = self.client.get( - self.toplist_urls['opml'], data={'scale_logo': 0}, **self.extra + self.toplist_urls["opml"], data={"scale_logo": 0}, **self.extra ) self.assertEqual(response.status_code, 400, response.content) def test_get_toplist_non_numeric_scale(self): response = self.client.get( - self.toplist_urls['txt'], data={'scale_logo': 'a'}, **self.extra + self.toplist_urls["txt"], data={"scale_logo": "a"}, **self.extra ) self.assertEqual(response.status_code, 400, response.content) def test_get_toplist_valid_empty(self): - response = self.client.get(self.toplist_urls['json'], **self.extra) + response = self.client.get(self.toplist_urls["json"], **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_search_non_numeric_scale_logo(self): - data = {'scale_logo': 'a'} + data = {"scale_logo": "a"} expected_status = 400 - expected_content = b'scale_logo has to be a numeric value' + expected_content = b"scale_logo has to be a numeric value" self._test_response_for_data( - self.search_urls['json'], data, expected_status, expected_content + self.search_urls["json"], data, expected_status, expected_content ) def test_search_scale_out_of_range(self): - data = {'scale_logo': 3000} + data = {"scale_logo": 3000} expected_status = 400 - expected_content = b'scale_logo has to be a number from 1 to 256' + expected_content = b"scale_logo has to be a number from 1 to 256" self._test_response_for_data( - self.search_urls['opml'], data, expected_status, expected_content + self.search_urls["opml"], data, expected_status, expected_content ) def test_search_no_query(self): - data = {'scale_logo': 1} + data = {"scale_logo": 1} expected_status = 400 - expected_content = b'/search.opml|txt|json?q={query}' + expected_content = b"/search.opml|txt|json?q={query}" self._test_response_for_data( - self.search_urls['opml'], data, expected_status, expected_content + self.search_urls["opml"], data, expected_status, expected_content ) def test_search_valid_query_status(self): - data = {'scale_logo': 1, 'q': 'foo'} + data = {"scale_logo": 1, "q": "foo"} expected_status = 200 - response = self.client.get(self.search_urls['json'], data) + response = self.client.get(self.search_urls["json"], data) self.assertEqual(response.status_code, expected_status) @@ -461,4 +461,4 @@ class OpenAPIDefinitionValidityTest(TestCase): "Test the validity of the OpenAPI definition file" def test_api_definition_validity(self): - validate_spec_url('file://' + os.path.abspath('./mygpo/api/openapi.yaml')) + validate_spec_url("file://" + os.path.abspath("./mygpo/api/openapi.yaml")) diff --git a/mygpo/api/urls.py b/mygpo/api/urls.py index 131e53356..d55092893 100644 --- a/mygpo/api/urls.py +++ b/mygpo/api/urls.py @@ -5,93 +5,97 @@ from mygpo.users import converters from mygpo.usersettings.converters import ScopeConverter -register_converter(converters.ClientUIDConverter, 'client-uid') -register_converter(converters.UsernameConverter, 'username') -register_converter(ScopeConverter, 'scope') +register_converter(converters.ClientUIDConverter, "client-uid") +register_converter(converters.UsernameConverter, "username") +register_converter(ScopeConverter, "scope") urlpatterns = [ - path('upload', legacy.upload), - path('getlist', legacy.getlist), + path("upload", legacy.upload), + path("getlist", legacy.getlist), path( - 'subscriptions//' '.', + "subscriptions//" ".", simple.subscriptions, - name='api-simple-subscriptions', + name="api-simple-subscriptions", ), path( - 'subscriptions/.', + "subscriptions/.", simple.all_subscriptions, - name='api-all-subscriptions', + name="api-all-subscriptions", ), - path('search.', simple.search, name='api-simple-search',), path( - 'suggestions/.', + "search.", + simple.search, + name="api-simple-search", + ), + path( + "suggestions/.", simple.suggestions, - name='suggestions-opml', + name="suggestions-opml", ), path( - 'toplist.opml', + "toplist.opml", simple.toplist, - kwargs={'count': 50, 'format': 'opml'}, - name='api-simple-toplist.opml', + kwargs={"count": 50, "format": "opml"}, + name="api-simple-toplist.opml", ), - path('toplist/.', simple.toplist, name='api-simple-toplist'), + path("toplist/.", simple.toplist, name="api-simple-toplist"), path( - 'toplist.', + "toplist.", simple.toplist, - kwargs={'count': 50}, - name='api-simple-toplist-50', + kwargs={"count": 50}, + name="api-simple-toplist-50", ), - path('gpodder-examples.', simple.example_podcasts, name='example-opml'), + path("gpodder-examples.", simple.example_podcasts, name="example-opml"), path( - 'api//subscriptions//' - '.json', + "api//subscriptions//" + ".json", subscriptions.SubscriptionsAPI.as_view(), - name='subscriptions-api', + name="subscriptions-api", ), - path('api//episodes/.json', advanced.episodes), + path("api//episodes/.json", advanced.episodes), path( - 'api//devices//' '.json', + "api//devices//" ".json", advanced.device, ), - path('api//devices/.json', advanced.devices), - path('api/2/auth//login.json', auth.login), - path('api/2/auth//logout.json', auth.logout), - path('api/2/tags/.json', advanced.directory.top_tags), - path('api/2/tag//.json', advanced.directory.tag_podcasts), + path("api//devices/.json", advanced.devices), + path("api/2/auth//login.json", auth.login), + path("api/2/auth//logout.json", auth.logout), + path("api/2/tags/.json", advanced.directory.top_tags), + path("api/2/tag//.json", advanced.directory.tag_podcasts), path( - 'api/2/data/podcast.json', + "api/2/data/podcast.json", advanced.directory.podcast_info, - name='api-podcast-info', + name="api-podcast-info", ), path( - 'api/2/data/episode.json', + "api/2/data/episode.json", advanced.directory.episode_info, - name='api-episode-info', + name="api-episode-info", ), - path('api/2/podcasts/create', advanced.directory.add_podcast), + path("api/2/podcasts/create", advanced.directory.add_podcast), path( - 'api/2/task/', + "api/2/task/", advanced.directory.add_podcast_status, - name='api-add-podcast-status', + name="api-add-podcast-status", ), - path('api/2/chapters/.json', episode.ChaptersAPI.as_view()), + path("api/2/chapters/.json", episode.ChaptersAPI.as_view()), path( - 'api/2/updates//.json', + "api/2/updates//.json", updates.DeviceUpdates.as_view(), ), path( - 'api/2/settings//.json', + "api/2/settings//.json", settings.SettingsAPI.as_view(), - name='settings-api', + name="settings-api", ), - path('api/2/favorites/.json', advanced.favorites), - path('api/2/lists//create.', lists.create), - path('api/2/lists/.json', lists.get_lists), + path("api/2/favorites/.json", advanced.favorites), + path("api/2/lists//create.", lists.create), + path("api/2/lists/.json", lists.get_lists), path( - 'api/2/lists//list/.', + "api/2/lists//list/.", lists.podcast_list, - name='api-get-list', + name="api-get-list", ), - path('api/2/sync-devices/.json', sync.main), + path("api/2/sync-devices/.json", sync.main), ] diff --git a/mygpo/asgi.py b/mygpo/asgi.py index d7f482783..e125fe830 100644 --- a/mygpo/asgi.py +++ b/mygpo/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mygpo.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mygpo.settings") application = get_asgi_application() diff --git a/mygpo/categories/admin.py b/mygpo/categories/admin.py index 865b0a9a1..f1db21d57 100644 --- a/mygpo/categories/admin.py +++ b/mygpo/categories/admin.py @@ -6,7 +6,7 @@ class CategoryEntryInline(admin.TabularInline): model = CategoryEntry - raw_id_fields = ('podcast',) + raw_id_fields = ("podcast",) class CategoryTagInline(admin.TabularInline): @@ -18,11 +18,11 @@ class CategoryAdmin(admin.ModelAdmin): model = Category - list_display = ('title', 'num_entries', 'tag_list') + list_display = ("title", "num_entries", "tag_list") show_full_result_count = False inlines = [CategoryEntryInline, CategoryTagInline] def tag_list(self, category): - return ', '.join(t.tag for t in category.tags.all()[:10]) + return ", ".join(t.tag for t in category.tags.all()[:10]) diff --git a/mygpo/categories/migrations/0001_initial_merged.py b/mygpo/categories/migrations/0001_initial_merged.py index 66c2315ad..1de7a2f5d 100644 --- a/mygpo/categories/migrations/0001_initial_merged.py +++ b/mygpo/categories/migrations/0001_initial_merged.py @@ -8,78 +8,78 @@ class Migration(migrations.Migration): replaces = [ - ('categories', '0001_initial'), - ('categories', '0002_auto_20140927_1501'), - ('categories', '0003_category_num_entries'), - ('categories', '0004_auto_20140927_1540'), + ("categories", "0001_initial"), + ("categories", "0002_auto_20140927_1501"), + ("categories", "0003_category_num_entries"), + ("categories", "0004_auto_20140927_1540"), ] - dependencies = [('podcasts', '0029_episode_index_toplist')] + dependencies = [("podcasts", "0029_episode_index_toplist")] operations = [ migrations.CreateModel( - name='Category', + name="Category", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('title', models.CharField(unique=True, max_length=1000)), + ("title", models.CharField(unique=True, max_length=1000)), ], options={}, bases=(models.Model,), ), migrations.CreateModel( - name='CategoryEntry', + name="CategoryEntry", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True, db_index=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True, db_index=True)), ( - 'category', + "category", models.ForeignKey( - to='categories.Category', on_delete=models.CASCADE + to="categories.Category", on_delete=models.CASCADE ), ), ( - 'podcast', - models.ForeignKey(to='podcasts.Podcast', on_delete=models.CASCADE), + "podcast", + models.ForeignKey(to="podcasts.Podcast", on_delete=models.CASCADE), ), ], options={}, bases=(models.Model,), ), migrations.CreateModel( - name='CategoryTag', + name="CategoryTag", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('tag', models.SlugField(unique=True)), + ("tag", models.SlugField(unique=True)), ( - 'category', + "category", models.ForeignKey( - related_name='tags', - to='categories.Category', + related_name="tags", + to="categories.Category", on_delete=models.CASCADE, ), ), @@ -88,15 +88,15 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='categoryentry', unique_together=set([('category', 'podcast')]) + name="categoryentry", unique_together=set([("category", "podcast")]) ), migrations.AlterModelOptions( - name='category', - options={'verbose_name': 'Category', 'verbose_name_plural': 'Categories'}, + name="category", + options={"verbose_name": "Category", "verbose_name_plural": "Categories"}, ), migrations.AddField( - model_name='category', - name='created', + model_name="category", + name="created", field=models.DateTimeField( default=datetime.datetime(2014, 9, 28, 13, 26, 28, 914_038), auto_now_add=True, @@ -104,8 +104,8 @@ class Migration(migrations.Migration): preserve_default=False, ), migrations.AddField( - model_name='category', - name='modified', + model_name="category", + name="modified", field=models.DateTimeField( default=datetime.datetime(2014, 9, 28, 13, 26, 28, 914_095), auto_now=True, @@ -113,32 +113,32 @@ class Migration(migrations.Migration): preserve_default=False, ), migrations.AlterField( - model_name='categoryentry', - name='category', + model_name="categoryentry", + name="category", field=models.ForeignKey( - related_name='entries', - to='categories.Category', + related_name="entries", + to="categories.Category", on_delete=models.CASCADE, ), ), migrations.AlterField( - model_name='categoryentry', - name='modified', + model_name="categoryentry", + name="modified", field=models.DateTimeField(auto_now=True), ), migrations.AlterIndexTogether( - name='category', index_together=set([('modified',)]) + name="category", index_together=set([("modified",)]) ), migrations.AlterIndexTogether( - name='categoryentry', index_together=set([('category', 'modified')]) + name="categoryentry", index_together=set([("category", "modified")]) ), migrations.AddField( - model_name='category', - name='num_entries', + model_name="category", + name="num_entries", field=models.IntegerField(default=0), preserve_default=False, ), migrations.AlterIndexTogether( - name='category', index_together=set([('modified', 'num_entries')]) + name="category", index_together=set([("modified", "num_entries")]) ), ] diff --git a/mygpo/categories/models.py b/mygpo/categories/models.py index bbd05c0da..94ec7bcca 100644 --- a/mygpo/categories/models.py +++ b/mygpo/categories/models.py @@ -12,10 +12,10 @@ class Category(UpdateInfoModel): num_entries = models.IntegerField() class Meta: - verbose_name = 'Category' - verbose_name_plural = 'Categories' + verbose_name = "Category" + verbose_name_plural = "Categories" - index_together = [('modified', 'num_entries')] + index_together = [("modified", "num_entries")] def save(self, *args, **kwargs): self.num_entries = self.entries.count() @@ -23,11 +23,11 @@ def save(self, *args, **kwargs): @property def podcasts(self): - return self.entries.prefetch_related('podcast', 'podcast__slugs') + return self.entries.prefetch_related("podcast", "podcast__slugs") @property def clean_title(self): - return self.title.replace('\n', ' ') + return self.title.replace("\n", " ") @property def tag(self): @@ -38,15 +38,15 @@ class CategoryEntry(UpdateInfoModel): """ A podcast in a category """ category = models.ForeignKey( - Category, related_name='entries', on_delete=models.CASCADE + Category, related_name="entries", on_delete=models.CASCADE ) podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE) class Meta: - unique_together = [('category', 'podcast')] + unique_together = [("category", "podcast")] - index_together = [('category', 'modified')] + index_together = [("category", "modified")] class CategoryTag(models.Model): @@ -54,5 +54,5 @@ class CategoryTag(models.Model): tag = models.SlugField(unique=True) category = models.ForeignKey( - Category, related_name='tags', on_delete=models.CASCADE + Category, related_name="tags", on_delete=models.CASCADE ) diff --git a/mygpo/celery.py b/mygpo/celery.py index 1e9ba704f..86634da78 100644 --- a/mygpo/celery.py +++ b/mygpo/celery.py @@ -4,8 +4,8 @@ from celery import Celery -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mygpo.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mygpo.settings") -celery = Celery('mygpo.celery') -celery.config_from_object('django.conf:settings', namespace='CELERY') +celery = Celery("mygpo.celery") +celery.config_from_object("django.conf:settings", namespace="CELERY") celery.autodiscover_tasks() diff --git a/mygpo/chapters/migrations/0001_initial.py b/mygpo/chapters/migrations/0001_initial.py index 8d1c90469..c58af3bb3 100644 --- a/mygpo/chapters/migrations/0001_initial.py +++ b/mygpo/chapters/migrations/0001_initial.py @@ -9,40 +9,40 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('podcasts', '0023_auto_20140729_1711'), + ("podcasts", "0023_auto_20140729_1711"), ] operations = [ migrations.CreateModel( - name='Chapter', + name="Chapter", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('start', models.IntegerField()), - ('end', models.IntegerField()), - ('label', models.CharField(max_length=100)), - ('advertisement', models.BooleanField(default=False)), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), + ("start", models.IntegerField()), + ("end", models.IntegerField()), + ("label", models.CharField(max_length=100)), + ("advertisement", models.BooleanField(default=False)), ( - 'episode', - models.ForeignKey(to='podcasts.Episode', on_delete=models.CASCADE), + "episode", + models.ForeignKey(to="podcasts.Episode", on_delete=models.CASCADE), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), ), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ) ] diff --git a/mygpo/chapters/migrations/0002_chapter_index.py b/mygpo/chapters/migrations/0002_chapter_index.py index 05abf9e03..bb76a47bd 100644 --- a/mygpo/chapters/migrations/0002_chapter_index.py +++ b/mygpo/chapters/migrations/0002_chapter_index.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): - dependencies = [('chapters', '0001_initial')] + dependencies = [("chapters", "0001_initial")] operations = [ migrations.AlterIndexTogether( - name='chapter', + name="chapter", index_together=set( - [('user', 'episode', 'created'), ('episode', 'user', 'start', 'end')] + [("user", "episode", "created"), ("episode", "user", "start", "end")] ), ) ] diff --git a/mygpo/chapters/models.py b/mygpo/chapters/models.py index f5e3c781a..3588e7d13 100644 --- a/mygpo/chapters/models.py +++ b/mygpo/chapters/models.py @@ -26,6 +26,6 @@ class Chapter(UpdateInfoModel): class Meta: index_together = [ - ('user', 'episode', 'created'), - ('episode', 'user', 'start', 'end'), + ("user", "episode", "created"), + ("episode", "user", "start", "end"), ] diff --git a/mygpo/constants.py b/mygpo/constants.py index cc9569530..1ba510965 100644 --- a/mygpo/constants.py +++ b/mygpo/constants.py @@ -4,4 +4,4 @@ PODCAST_LOGO_MEDIUM_SIZE = 64 PODCAST_LOGO_BIG_SIZE = 128 -DEFAULT_LOGIN_REDIRECT = '/' +DEFAULT_LOGIN_REDIRECT = "/" diff --git a/mygpo/core/models.py b/mygpo/core/models.py index 746361dde..b33faf7d3 100644 --- a/mygpo/core/models.py +++ b/mygpo/core/models.py @@ -30,11 +30,11 @@ class GenericManager(models.Manager): """ Generic manager methods """ def count_fast(self): - """ Fast approximate count of all model instances + """Fast approximate count of all model instances PostgreSQL is slow when counting records without an index. This is a workaround which only gives approximate results. see: - http://wiki.postgresql.org/wiki/Slow_Counting """ + http://wiki.postgresql.org/wiki/Slow_Counting""" cursor = connection.cursor() cursor.execute( "select reltuples from pg_class where relname='%s';" @@ -65,7 +65,7 @@ class Meta: class OrderedModel(models.Model): - """ A model that can be ordered + """A model that can be ordered The implementing Model must make sure that 'order' is sufficiently unique """ @@ -74,11 +74,11 @@ class OrderedModel(models.Model): class Meta: abstract = True - ordering = ['order'] + ordering = ["order"] class OptionallyOrderedModel(models.Model): - """ A model that can be ordered, w/ unknown order of individual objects + """A model that can be ordered, w/ unknown order of individual objects The implementing Model must make sure that 'order' is sufficiently unique """ @@ -87,4 +87,4 @@ class OptionallyOrderedModel(models.Model): class Meta: abstract = True - ordering = ['order'] + ordering = ["order"] diff --git a/mygpo/core/slugs.py b/mygpo/core/slugs.py index 3f4227785..0abae2b2a 100644 --- a/mygpo/core/slugs.py +++ b/mygpo/core/slugs.py @@ -18,9 +18,9 @@ def _get_base_slug(obj): return base_slug def __iter__(self): - """ Generates possible slugs + """Generates possible slugs - The consumer can can consume until it get's an unused one """ + The consumer can can consume until it get's an unused one""" if self.obj.slug: # The object already has a slug @@ -33,7 +33,7 @@ def __iter__(self): yield str(self.base_slug) for n in count(1): - tmp_slug = '%s-%d' % (self.base_slug, n) + tmp_slug = "%s-%d" % (self.base_slug, n) # slugify returns SafeUnicode, we need a plain string yield str(tmp_slug) @@ -57,7 +57,7 @@ def _get_base_slug(self, podcast): if podcast.group_member_name: member_slug = slugify(podcast.group_member_name) if member_slug and not member_slug in base_slug: - base_slug = '%s-%s' % (base_slug, member_slug) + base_slug = "%s-%s" % (base_slug, member_slug) return base_slug diff --git a/mygpo/data/__init__.py b/mygpo/data/__init__.py index 11086c34e..f629514ee 100644 --- a/mygpo/data/__init__.py +++ b/mygpo/data/__init__.py @@ -1 +1 @@ -default_app_config = 'mygpo.data.apps.DataAppConfig' +default_app_config = "mygpo.data.apps.DataAppConfig" diff --git a/mygpo/data/admin.py b/mygpo/data/admin.py index af7355aca..9c33b0328 100644 --- a/mygpo/data/admin.py +++ b/mygpo/data/admin.py @@ -7,18 +7,18 @@ class PodcastUpdateResultAdmin(admin.ModelAdmin): model = models.PodcastUpdateResult - list_display = ['title', 'start', 'duration', 'successful', 'episodes_added'] + list_display = ["title", "start", "duration", "successful", "episodes_added"] readonly_fields = [ - 'id', - 'podcast_url', - 'podcast', - 'start', - 'duration', - 'successful', - 'error_message', - 'podcast_created', - 'episodes_added', + "id", + "podcast_url", + "podcast", + "start", + "duration", + "successful", + "error_message", + "podcast_created", + "episodes_added", ] def title(self, res): diff --git a/mygpo/data/apps.py b/mygpo/data/apps.py index 8fc81fd36..322cdc4c1 100644 --- a/mygpo/data/apps.py +++ b/mygpo/data/apps.py @@ -15,9 +15,9 @@ def update_podcast(sender, **kwargs): class DataAppConfig(AppConfig): - name = 'mygpo.data' + name = "mygpo.data" def ready(self): subscription_updated.connect( - update_podcast, dispatch_uid='update_podcast-pubsub' + update_podcast, dispatch_uid="update_podcast-pubsub" ) diff --git a/mygpo/data/delicious.py b/mygpo/data/delicious.py index 12873bffa..ee46bebb7 100644 --- a/mygpo/data/delicious.py +++ b/mygpo/data/delicious.py @@ -12,17 +12,17 @@ def get_tags(url): """ split = urllib.parse.urlsplit(url) - if split.path == '': + if split.path == "": split = urllib.parse.SplitResult( - split.scheme, split.netloc, '/', split.query, split.fragment + split.scheme, split.netloc, "/", split.query, split.fragment ) url = split.geturl() m = hashlib.md5() - m.update(url.encode('ascii')) + m.update(url.encode("ascii")) url_md5 = m.hexdigest() - req = 'http://feeds.delicious.com/v2/json/urlinfo/%s' % url_md5 + req = "http://feeds.delicious.com/v2/json/urlinfo/%s" % url_md5 resp = urllib.request.urlopen(req).read() try: @@ -32,9 +32,9 @@ def get_tags(url): tags = {} for o in resp_obj: - if (not 'top_tags' in o) or (not o['top_tags']): + if (not "top_tags" in o) or (not o["top_tags"]): return {} - for tag, count in o['top_tags'].items(): + for tag, count in o["top_tags"].items(): tags[tag] = count return tags diff --git a/mygpo/data/feeddownloader.py b/mygpo/data/feeddownloader.py index 6708f482c..dfe36ab9e 100755 --- a/mygpo/data/feeddownloader.py +++ b/mygpo/data/feeddownloader.py @@ -53,9 +53,9 @@ def update_podcasts(queue): """ Fetch data for the URLs supplied as the queue iterable """ for n, podcast_url in enumerate(queue, 1): - logger.info('Update %d - %s', n, podcast_url) + logger.info("Update %d - %s", n, podcast_url) if not podcast_url: - logger.warning('Podcast URL empty, skipping') + logger.warning("Podcast URL empty, skipping") continue try: @@ -63,10 +63,10 @@ def update_podcasts(queue): yield updater.update_podcast() except NoPodcastCreated as npc: - logger.info('No podcast created: %s', npc) + logger.info("No podcast created: %s", npc) except NoEpisodesException as nee: - logger.info(f'No episodes found when parsing {podcast_url}') + logger.info(f"No episodes found when parsing {podcast_url}") continue except GeneratorExit: @@ -82,7 +82,7 @@ class PodcastUpdater(object): def __init__(self, podcast_url): self.podcast_url = ( - (podcast_url[:2046] + '..') if len(podcast_url) > 2048 else podcast_url + (podcast_url[:2046] + "..") if len(podcast_url) > 2048 else podcast_url ) def update_podcast(self): @@ -107,11 +107,11 @@ def update_podcast(self): if not parsed: # if it exists already, we mark it as outdated self._mark_outdated( - podcast, 'error while fetching feed', episode_updater + podcast, "error while fetching feed", episode_updater ) return - episode_updater.update_episodes(parsed.get('episodes', [])) + episode_updater.update_episodes(parsed.get("episodes", [])) podcast.refresh_from_db() podcast.episode_count = episode_updater.count_episodes() @@ -129,7 +129,7 @@ def parse_feed(self): self._validate_parsed(parsed) except (requests.exceptions.RequestException, NoEpisodesException) as ex: - logger.warn('Error while fetching/parsing feed', exc_info=True) + logger.warn("Error while fetching/parsing feed", exc_info=True) # if we fail to parse the URL, we don't even create the # podcast object @@ -146,9 +146,9 @@ def parse_feed(self): return (parsed, podcast, created) def _fetch_feed(self): - params = {'url': self.podcast_url, 'process_text': 'markdown'} - headers = {'Accept': 'application/json'} - url = urljoin(settings.FEEDSERVICE_URL, 'parse') + params = {"url": self.podcast_url, "process_text": "markdown"} + headers = {"Accept": "application/json"} + url = urljoin(settings.FEEDSERVICE_URL, "parse") r = requests.get(url, params=params, headers=headers, timeout=30) if r.status_code != 200: @@ -168,13 +168,13 @@ def _fetch_feed(self): raise def _validate_parsed(self, parsed): - """ validates the parsed results and raises an exception if invalid + """validates the parsed results and raises an exception if invalid feedparser parses pretty much everything. We reject anything that doesn't look like a feed""" - if not parsed or not parsed.get('episodes', []): - raise NoEpisodesException('no episodes found') + if not parsed or not parsed.get("episodes", []): + raise NoEpisodesException("no episodes found") def _update_podcast(self, podcast, parsed, episode_updater, update_result): """ updates a podcast according to new parser results """ @@ -185,41 +185,41 @@ def _update_podcast(self, podcast, parsed, episode_updater, update_result): # will later be used to see whether the index is outdated old_index_fields = get_index_fields(podcast) - podcast.title = parsed.get('title') or podcast.title - podcast.description = parsed.get('description') or podcast.description - podcast.subtitle = parsed.get('subtitle') or podcast.subtitle - podcast.link = parsed.get('link') or podcast.link - podcast.logo_url = parsed.get('logo') or podcast.logo_url + podcast.title = parsed.get("title") or podcast.title + podcast.description = parsed.get("description") or podcast.description + podcast.subtitle = parsed.get("subtitle") or podcast.subtitle + podcast.link = parsed.get("link") or podcast.link + podcast.logo_url = parsed.get("logo") or podcast.logo_url podcast.author = to_maxlength( - Podcast, 'author', parsed.get('author') or podcast.author + Podcast, "author", parsed.get("author") or podcast.author ) podcast.language = to_maxlength( - Podcast, 'language', parsed.get('language') or podcast.language + Podcast, "language", parsed.get("language") or podcast.language ) podcast.content_types = ( - ','.join(parsed.get('content_types')) or podcast.content_types + ",".join(parsed.get("content_types")) or podcast.content_types ) # podcast.tags['feed'] = parsed.tags or podcast.tags.get('feed', []) podcast.common_episode_title = to_maxlength( Podcast, - 'common_episode_title', - parsed.get('common_title') or podcast.common_episode_title, + "common_episode_title", + parsed.get("common_title") or podcast.common_episode_title, ) - podcast.new_location = parsed.get('new_location') or podcast.new_location + podcast.new_location = parsed.get("new_location") or podcast.new_location podcast.flattr_url = to_maxlength( - Podcast, 'flattr_url', parsed.get('flattr') or podcast.flattr_url + Podcast, "flattr_url", parsed.get("flattr") or podcast.flattr_url ) - podcast.hub = parsed.get('hub') or podcast.hub - podcast.license = parsed.get('license') or podcast.license + podcast.hub = parsed.get("hub") or podcast.hub + podcast.license = parsed.get("license") or podcast.license podcast.max_episode_order = episode_updater.max_episode_order - podcast.add_missing_urls(parsed.get('urls', [])) + podcast.add_missing_urls(parsed.get("urls", [])) if podcast.new_location: try: @@ -227,7 +227,7 @@ def _update_podcast(self, podcast, parsed, episode_updater, update_result): if new_podcast != podcast: self._mark_outdated( - podcast, 'redirected to different podcast', episode_updater + podcast, "redirected to different podcast", episode_updater ) return except Podcast.DoesNotExist: @@ -236,7 +236,7 @@ def _update_podcast(self, podcast, parsed, episode_updater, update_result): # latest episode timestamp episodes = Episode.objects.filter( podcast=podcast, released__isnull=False - ).order_by('released') + ).order_by("released") # Determine update interval @@ -274,14 +274,14 @@ def _update_podcast(self, podcast, parsed, episode_updater, update_result): # The podcast is always saved (not just when there are changes) because # we need to record the last update - logger.info('Saving podcast.') + logger.info("Saving podcast.") podcast.last_update = datetime.utcnow() podcast.save() try: subscribe_at_hub(podcast) except SubscriptionError as se: - logger.warning('subscribing to hub failed: %s', str(se)) + logger.warning("subscribing to hub failed: %s", str(se)) self.assign_slug(podcast) episode_updater.assign_missing_episode_slugs() @@ -324,7 +324,7 @@ def _update_categories(self, podcast, prev_timestamp): update_category(podcast) def _mark_outdated(self, podcast, msg, episode_updater): - logger.info('marking podcast outdated: %s', msg) + logger.info("marking podcast outdated: %s", msg) podcast.outdated = True podcast.last_update = datetime.utcnow() podcast.save() @@ -344,18 +344,18 @@ def update_episodes(self, parsed_episodes): episodes_to_update = list(islice(parsed_episodes, 0, MAX_EPISODES_UPDATE)) logger.info( - 'Parsed %d (%d) episodes', len(parsed_episodes), len(episodes_to_update) + "Parsed %d (%d) episodes", len(parsed_episodes), len(episodes_to_update) ) - logger.info('Updating %d episodes', len(episodes_to_update)) + logger.info("Updating %d episodes", len(episodes_to_update)) for n, parsed in enumerate(episodes_to_update, 1): url = self.get_episode_url(parsed) if not url: - logger.info('Skipping episode %d for missing URL', n) + logger.info("Skipping episode %d for missing URL", n) continue - logger.info('Updating episode %d / %d', n, len(parsed_episodes)) + logger.info("Updating episode %d / %d", n, len(parsed_episodes)) episode, created = Episode.objects.get_or_create_for_url(self.podcast, url) @@ -373,17 +373,17 @@ def update_episodes(self, parsed_episodes): ] outdated_episodes = set(current_episodes) - set(self.updated_episodes) - logger.info('Marking %d episodes as outdated', len(outdated_episodes)) + logger.info("Marking %d episodes as outdated", len(outdated_episodes)) for episode in outdated_episodes: updater = EpisodeUpdater(episode, self.podcast) updater.mark_outdated() @transaction.atomic def order_episodes(self): - """ Reorder the podcast's episode according to release timestamp + """Reorder the podcast's episode according to release timestamp Returns the highest order value (corresponding to the most recent - episode) """ + episode)""" num_episodes = self.podcast.episode_count if not num_episodes: @@ -391,9 +391,9 @@ def order_episodes(self): episodes = ( self.podcast.episode_set.all() - .extra(select={'has_released': 'released IS NOT NULL'}) - .order_by('-has_released', '-released', 'pk') - .only('pk') + .extra(select={"has_released": "released IS NOT NULL"}) + .order_by("-has_released", "-released", "pk") + .only("pk") ) for n, episode in enumerate(episodes.iterator(), 1): @@ -405,7 +405,7 @@ def order_episodes(self): if episode.order == new_order: continue - logger.info('Updating order from {} to {}'.format(episode.order, new_order)) + logger.info("Updating order from {} to {}".format(episode.order, new_order)) episode.order = new_order episode.save() @@ -413,9 +413,9 @@ def order_episodes(self): def get_episode_url(self, parsed_episode): """ returns the URL of a parsed episode """ - for f in parsed_episode.get('files', []): - if f.get('urls', []): - return f['urls'][0] + for f in parsed_episode.get("files", []): + if f.get("urls", []): + return f["urls"][0] return None def count_episodes(self): @@ -427,7 +427,7 @@ def get_update_interval(self, episodes): count = episodes.count() if not count: logger.info( - 'no episodes, using default interval of %dh', DEFAULT_UPDATE_INTERVAL + "no episodes, using default interval of %dh", DEFAULT_UPDATE_INTERVAL ) return DEFAULT_UPDATE_INTERVAL @@ -439,7 +439,7 @@ def get_update_interval(self, episodes): interval = int(timespan_h / count) logger.info( - '%d episodes in %d days => %dh interval', count, timespan_h / 24, interval + "%d episodes in %d days => %dh interval", count, timespan_h / 24, interval ) # place interval between {MIN,MAX}_UPDATE_INTERVAL @@ -478,60 +478,60 @@ def update_episode(self, parsed_episode): # TODO: check if there have been any changes, to # avoid unnecessary updates self.episode.guid = to_maxlength( - Episode, 'guid', parsed_episode.get('guid') or self.episode.guid + Episode, "guid", parsed_episode.get("guid") or self.episode.guid ) self.episode.description = ( - parsed_episode.get('description') or self.episode.description + parsed_episode.get("description") or self.episode.description ) - self.episode.subtitle = parsed_episode.get('subtitle') or self.episode.subtitle + self.episode.subtitle = parsed_episode.get("subtitle") or self.episode.subtitle self.episode.content = ( - parsed_episode.get('content') - or parsed_episode.get('description') + parsed_episode.get("content") + or parsed_episode.get("description") or self.episode.content ) self.episode.link = to_maxlength( - Episode, 'link', parsed_episode.get('link') or self.episode.link + Episode, "link", parsed_episode.get("link") or self.episode.link ) self.episode.released = ( - datetime.utcfromtimestamp(parsed_episode.get('released')) - if parsed_episode.get('released') + datetime.utcfromtimestamp(parsed_episode.get("released")) + if parsed_episode.get("released") else self.episode.released ) self.episode.author = to_maxlength( - Episode, 'author', parsed_episode.get('author') or self.episode.author + Episode, "author", parsed_episode.get("author") or self.episode.author ) - self.episode.duration = parsed_episode.get('duration') or self.episode.duration + self.episode.duration = parsed_episode.get("duration") or self.episode.duration - self.episode.filesize = parsed_episode['files'][0]['filesize'] + self.episode.filesize = parsed_episode["files"][0]["filesize"] self.episode.language = ( - parsed_episode.get('language') + parsed_episode.get("language") or self.episode.language or self.podcast.language ) - mimetypes = [f['mimetype'] for f in parsed_episode.get('files', [])] - self.episode.mimetypes = ','.join(list(set(filter(None, mimetypes)))) + mimetypes = [f["mimetype"] for f in parsed_episode.get("files", [])] + self.episode.mimetypes = ",".join(list(set(filter(None, mimetypes)))) self.episode.flattr_url = to_maxlength( Episode, - 'flattr_url', - parsed_episode.get('flattr') or self.episode.flattr_url, + "flattr_url", + parsed_episode.get("flattr") or self.episode.flattr_url, ) - self.episode.license = parsed_episode.get('license') or self.episode.license + self.episode.license = parsed_episode.get("license") or self.episode.license self.episode.title = to_maxlength( Episode, - 'title', - parsed_episode.get('title') + "title", + parsed_episode.get("title") or self.episode.title or file_basename_no_extension(self.episode.url), ) @@ -541,7 +541,7 @@ def update_episode(self, parsed_episode): parsed_urls = list( chain.from_iterable( - f.get('urls', []) for f in parsed_episode.get('files', []) + f.get("urls", []) for f in parsed_episode.get("files", []) ) ) self.episode.add_missing_urls(parsed_urls) @@ -557,7 +557,7 @@ def mark_outdated(self): def file_basename_no_extension(filename): - """ Returns filename without extension + """Returns filename without extension >>> file_basename_no_extension('/home/me/file.txt') 'file' diff --git a/mygpo/data/flickr.py b/mygpo/data/flickr.py index f841fa408..1e05e3ae3 100644 --- a/mygpo/data/flickr.py +++ b/mygpo/data/flickr.py @@ -9,11 +9,11 @@ logger = logging.getLogger(__name__) -GET_SIZES_TEMPLATE = 'https://api.flickr.com/services/rest/?method=flickr.photos.getSizes&api_key={api_key}&photo_id={photo_id}&format=json&nojsoncallback=1' +GET_SIZES_TEMPLATE = "https://api.flickr.com/services/rest/?method=flickr.photos.getSizes&api_key={api_key}&photo_id={photo_id}&format=json&nojsoncallback=1" def get_photo_sizes(photo_id): - """ Returns available sizes for the photo with the given ID + """Returns available sizes for the photo with the given ID Returns a list of dicts containing the following keys * source @@ -30,7 +30,7 @@ def get_photo_sizes(photo_id): try: resp = requests.get(url) except requests.exceptions.RequestException as e: - logger.warning('Retrieving Flickr photo sizes failed: %s', str(e)) + logger.warning("Retrieving Flickr photo sizes failed: %s", str(e)) return [] try: @@ -39,13 +39,13 @@ def get_photo_sizes(photo_id): return [] try: - return resp_obj['sizes']['size'] + return resp_obj["sizes"]["size"] except KeyError: return [] def get_photo_id(url): - """ Returns the Photo ID for a Photo URL + """Returns the Photo ID for a Photo URL >>> get_photo_id('https://farm9.staticflickr.com/8747/12346789012_bf1e234567_b.jpg') '12346789012' @@ -56,9 +56,9 @@ def get_photo_id(url): """ photo_id_re = [ - 'http://.*flickr.com/[^/]+/([^_]+)_.*', - 'https://.*staticflickr.com/[^/]+/([^_]+)_.*', - 'https?://.*flickr.com/photos/[^/]+/([^/]+).*', + "http://.*flickr.com/[^/]+/([^_]+)_.*", + "https://.*staticflickr.com/[^/]+/([^_]+)_.*", + "https?://.*flickr.com/photos/[^/]+/([^/]+).*", ] for regex in photo_id_re: @@ -68,7 +68,7 @@ def get_photo_id(url): def is_flickr_image(url): - """ Returns True if the URL represents a Flickr images + """Returns True if the URL represents a Flickr images >>> is_flickr_image('https://farm9.staticflickr.com/8747/12346789012_bf1e234567_b.jpg') True @@ -82,14 +82,14 @@ def is_flickr_image(url): if url is None: return False - return bool(re.search(r'flickr\.com.*\.(jpg|jpeg|png|gif)', url)) + return bool(re.search(r"flickr\.com.*\.(jpg|jpeg|png|gif)", url)) -def get_display_photo(url, label='Medium'): +def get_display_photo(url, label="Medium"): photo_id = get_photo_id(url) sizes = get_photo_sizes(photo_id) for s in sizes: - if s['label'] == label: - return s['source'] + if s["label"] == label: + return s["source"] return url diff --git a/mygpo/data/management/commands/feed-downloader.py b/mygpo/data/management/commands/feed-downloader.py index 5ae4e9499..ce2302917 100644 --- a/mygpo/data/management/commands/feed-downloader.py +++ b/mygpo/data/management/commands/feed-downloader.py @@ -19,9 +19,9 @@ def add_arguments(self, parser): super().add_arguments(parser) parser.add_argument( - '--list-only', - action='store_true', - dest='list', + "--list-only", + action="store_true", + dest="list", default=False, help="Don't update anything, just list podcasts ", ), @@ -30,16 +30,16 @@ def handle(self, *args, **options): queue = self.get_podcasts(*args, **options) - max_podcasts = options.get('max') + max_podcasts = options.get("max") if max_podcasts: queue = islice(queue, 0, max_podcasts) - if options.get('list'): + if options.get("list"): for podcast in queue: - logger.info('Podcast %s', podcast) + logger.info("Podcast %s", podcast) else: - logger.info('Updating podcasts...') + logger.info("Updating podcasts...") for podcast in update_podcasts(queue): - logger.info('Updated podcast %s', podcast) + logger.info("Updated podcast %s", podcast) diff --git a/mygpo/data/management/commands/group-podcasts.py b/mygpo/data/management/commands/group-podcasts.py index e8b47fd1c..313b259f8 100644 --- a/mygpo/data/management/commands/group-podcasts.py +++ b/mygpo/data/management/commands/group-podcasts.py @@ -10,18 +10,18 @@ def pairwise(t): class Command(BaseCommand): def add_arguments(self, parser): - parser.add_argument('title') - parser.add_argument('url1') - parser.add_argument('name1') - parser.add_argument('url2') - parser.add_argument('name2') + parser.add_argument("title") + parser.add_argument("url1") + parser.add_argument("name1") + parser.add_argument("url2") + parser.add_argument("name2") def handle(self, *args, **options): - p1_url = options['url1'] - p2_url = options['url2'] - group_title = options['title'] - myname = options['name1'] - othername = options['name2'] + p1_url = options["url1"] + p2_url = options["url2"] + group_title = options["title"] + myname = options["name1"] + othername = options["name2"] p1 = Podcast.objects.get(urls__url=p1_url) p2 = Podcast.objects.get(urls__url=p2_url) diff --git a/mygpo/data/management/commands/tag-downloader.py b/mygpo/data/management/commands/tag-downloader.py index ce950452e..c08e58623 100644 --- a/mygpo/data/management/commands/tag-downloader.py +++ b/mygpo/data/management/commands/tag-downloader.py @@ -6,7 +6,7 @@ from mygpo.maintenance.management.podcastcmd import PodcastCommand -SOURCE = 'delicious' +SOURCE = "delicious" class Command(PodcastCommand): diff --git a/mygpo/data/migrations/0001_initial.py b/mygpo/data/migrations/0001_initial.py index 38f50714f..dbf11c204 100644 --- a/mygpo/data/migrations/0001_initial.py +++ b/mygpo/data/migrations/0001_initial.py @@ -11,33 +11,33 @@ class Migration(migrations.Migration): initial = True - dependencies = [('podcasts', '0037_index_podcast_lastupdate')] + dependencies = [("podcasts", "0037_index_podcast_lastupdate")] operations = [ migrations.CreateModel( - name='PodcastUpdateResult', + name="PodcastUpdateResult", fields=[ - ('id', models.UUIDField(primary_key=True, serialize=False)), - ('start', models.DateTimeField(default=datetime.datetime.utcnow)), - ('duration', models.DurationField()), - ('successful', models.BooleanField()), - ('error_message', models.TextField()), - ('podcast_created', models.BooleanField()), - ('episodes_added', models.IntegerField()), + ("id", models.UUIDField(primary_key=True, serialize=False)), + ("start", models.DateTimeField(default=datetime.datetime.utcnow)), + ("duration", models.DurationField()), + ("successful", models.BooleanField()), + ("error_message", models.TextField()), + ("podcast_created", models.BooleanField()), + ("episodes_added", models.IntegerField()), ( - 'podcast', + "podcast", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to='podcasts.Podcast', + to="podcasts.Podcast", ), ), ], - options={'get_latest_by': 'start', 'ordering': ['-start']}, + options={"get_latest_by": "start", "ordering": ["-start"]}, ), migrations.AddIndex( - model_name='podcastupdateresult', + model_name="podcastupdateresult", index=models.Index( - fields=['podcast', 'start'], name='data_podcas_podcast_cf4cc1_idx' + fields=["podcast", "start"], name="data_podcas_podcast_cf4cc1_idx" ), ), ] diff --git a/mygpo/data/migrations/0002_result_podcast_null.py b/mygpo/data/migrations/0002_result_podcast_null.py index 7a94a3fd8..a39a2100b 100644 --- a/mygpo/data/migrations/0002_result_podcast_null.py +++ b/mygpo/data/migrations/0002_result_podcast_null.py @@ -8,16 +8,16 @@ class Migration(migrations.Migration): - dependencies = [('data', '0001_initial')] + dependencies = [("data", "0001_initial")] operations = [ migrations.AlterField( - model_name='podcastupdateresult', - name='podcast', + model_name="podcastupdateresult", + name="podcast", field=models.ForeignKey( null=True, on_delete=django.db.models.deletion.CASCADE, - to='podcasts.Podcast', + to="podcasts.Podcast", ), ) ] diff --git a/mygpo/data/migrations/0003_podcastupdateresult_podcast_url.py b/mygpo/data/migrations/0003_podcastupdateresult_podcast_url.py index 19233beec..2f6a184c2 100644 --- a/mygpo/data/migrations/0003_podcastupdateresult_podcast_url.py +++ b/mygpo/data/migrations/0003_podcastupdateresult_podcast_url.py @@ -7,13 +7,13 @@ class Migration(migrations.Migration): - dependencies = [('data', '0002_result_podcast_null')] + dependencies = [("data", "0002_result_podcast_null")] operations = [ migrations.AddField( - model_name='podcastupdateresult', - name='podcast_url', - field=models.URLField(default='unknown', max_length=2048), + model_name="podcastupdateresult", + name="podcast_url", + field=models.URLField(default="unknown", max_length=2048), preserve_default=False, ) ] diff --git a/mygpo/data/mimetype.py b/mygpo/data/mimetype.py index 575ec294b..b05aa6e80 100644 --- a/mygpo/data/mimetype.py +++ b/mygpo/data/mimetype.py @@ -10,7 +10,7 @@ TYPE_THRESHOLD = 0.2 -CONTENT_TYPES = (_('image'), _('audio'), _('video')) +CONTENT_TYPES = (_("image"), _("audio"), _("video")) def get_podcast_types(episodes): @@ -73,18 +73,18 @@ def get_type(mimetype): if not mimetype: return None - if '/' not in mimetype: + if "/" not in mimetype: return None - category, type = mimetype.split('/', 1) - if category in ('audio', 'video', 'image'): + category, type = mimetype.split("/", 1) + if category in ("audio", "video", "image"): return category - elif type == 'ogg': - return 'audio' - elif type == 'x-youtube': - return 'video' - elif type == 'x-vimeo': - return 'video' + elif type == "ogg": + return "audio" + elif type == "x-youtube": + return "video" + elif type == "x-vimeo": + return "video" def get_mimetype(mimetype, url): diff --git a/mygpo/data/models.py b/mygpo/data/models.py index 7eb5629a2..a2f10f543 100644 --- a/mygpo/data/models.py +++ b/mygpo/data/models.py @@ -9,9 +9,9 @@ class PodcastUpdateResult(UUIDModel): - """ Results of a podcast update + """Results of a podcast update - Once an instance is stored, the update is assumed to be finished. """ + Once an instance is stored, the update is assumed to be finished.""" # URL of the podcast to be updated podcast_url = models.URLField(max_length=2048) @@ -39,11 +39,11 @@ class PodcastUpdateResult(UUIDModel): class Meta(object): - get_latest_by = 'start' + get_latest_by = "start" - ordering = ['-start'] + ordering = ["-start"] - indexes = [models.Index(fields=['podcast', 'start'])] + indexes = [models.Index(fields=["podcast", "start"])] def __str__(self): return 'Update Result for "{}" @ {:%Y-%m-%d %H:%M}'.format( diff --git a/mygpo/data/podcast.py b/mygpo/data/podcast.py index 3f001112e..e054b2e19 100644 --- a/mygpo/data/podcast.py +++ b/mygpo/data/podcast.py @@ -12,23 +12,23 @@ def calc_similar_podcasts(podcast, num=20, user_sample=100): - """ Get a list of podcasts that seem to be similar to the given one. + """Get a list of podcasts that seem to be similar to the given one. The similarity is based on similar subscriptions; for performance - reasons, only a sample of subscribers is considered """ + reasons, only a sample of subscribers is considered""" - logger.info('Calculating podcasts similar to {podcast}'.format(podcast=podcast)) + logger.info("Calculating podcasts similar to {podcast}".format(podcast=podcast)) # get all users that subscribe to this podcast user_ids = ( Subscription.objects.filter(podcast=podcast) - .order_by('user') - .distinct('user') - .values_list('user', flat=True) + .order_by("user") + .distinct("user") + .values_list("user", flat=True) ) logger.info( - 'Found {num_subscribers} subscribers, taking a sample ' - 'of {sample_size}'.format( + "Found {num_subscribers} subscribers, taking a sample " + "of {sample_size}".format( num_subscribers=len(user_ids), sample_size=user_sample ) ) @@ -43,12 +43,12 @@ def calc_similar_podcasts(podcast, num=20, user_sample=100): for user_id in user_ids: subscriptions = ( Podcast.objects.filter(subscription__user__id__in=user_ids) - .distinct('pk') + .distinct("pk") .exclude(pk=podcast.pk) ) podcasts.update(Counter(subscriptions)) logger.info( - 'Found {num_podcasts}, returning top {num_results}'.format( + "Found {num_podcasts}, returning top {num_results}".format( num_podcasts=len(podcasts), num_results=num ) ) @@ -66,13 +66,13 @@ def subscribe_at_hub(podcast): if not base_url: logger.warning( - 'Could not subscribe to podcast {podcast} ' - 'at hub {hub} because DEFAULT_BASE_URL is not ' - 'set.'.format(podcast=podcast, hub=podcast.hub) + "Could not subscribe to podcast {podcast} " + "at hub {hub} because DEFAULT_BASE_URL is not " + "set.".format(podcast=podcast, hub=podcast.hub) ) return logger.info( - 'subscribing to {podcast} at {hub}.'.format(podcast=podcast, hub=podcast.hub) + "subscribing to {podcast} at {hub}.".format(podcast=podcast, hub=podcast.hub) ) utils.subscribe(podcast, podcast.url, podcast.hub, base_url) diff --git a/mygpo/data/tasks.py b/mygpo/data/tasks.py index 22d8c65ec..3bfdcb257 100644 --- a/mygpo/data/tasks.py +++ b/mygpo/data/tasks.py @@ -41,7 +41,7 @@ def update_related_podcasts(podcast_pk, max_related=20): podcast.related_podcasts.add(p) except IntegrityError: logger.warning( - 'Integrity error while adding related podcast', exc_info=True + "Integrity error while adding related podcast", exc_info=True ) @@ -62,8 +62,8 @@ def schedule_updates(interval=UPDATE_INTERVAL): podcasts = ( Podcast.objects.all() .next_update_between(now, now + interval) - .prefetch_related('urls') - .only('pk')[:max_updates] + .prefetch_related("urls") + .only("pk")[:max_updates] ) _schedule_updates(podcasts) @@ -77,17 +77,17 @@ def schedule_updates_longest_no_update(): # max number of updates to schedule (one every 20s) max_updates = UPDATE_INTERVAL.total_seconds() / 10 - podcasts = Podcast.objects.order_by('last_update')[:max_updates] + podcasts = Podcast.objects.order_by("last_update")[:max_updates] _schedule_updates(podcasts) def _schedule_updates(podcasts): """ Schedule updates for podcasts """ - logger.info('Scheduling %d podcasts for update', len(podcasts)) + logger.info("Scheduling %d podcasts for update", len(podcasts)) # queue all those podcast updates for podcast in podcasts: # update_podcasts.delay() seems to block other task execution, # therefore celery.send_task() is used instead urls = [podcast.url] - celery.send_task('mygpo.data.tasks.update_podcasts', args=[urls]) + celery.send_task("mygpo.data.tasks.update_podcasts", args=[urls]) diff --git a/mygpo/data/tests.py b/mygpo/data/tests.py index 201c8bd67..73a355650 100644 --- a/mygpo/data/tests.py +++ b/mygpo/data/tests.py @@ -8,37 +8,37 @@ import responses -MEDIUM_URL = 'https://farm6.staticflickr.com/5001/1246644888_36863b0856.jpg' +MEDIUM_URL = "https://farm6.staticflickr.com/5001/1246644888_36863b0856.jpg" API_RESPONSE = { - 'stat': 'ok', - 'sizes': { - 'canblog': 0, - 'size': [ + "stat": "ok", + "sizes": { + "canblog": 0, + "size": [ { - 'source': 'https://farm6.staticflickr.com/5001/1234533888_45673b0856_s.jpg', - 'url': 'https://www.flickr.com/photos/someuser/135643888/sizes/sq/', - 'media': 'photo', - 'height': 75, - 'width': 75, - 'label': 'Square', + "source": "https://farm6.staticflickr.com/5001/1234533888_45673b0856_s.jpg", + "url": "https://www.flickr.com/photos/someuser/135643888/sizes/sq/", + "media": "photo", + "height": 75, + "width": 75, + "label": "Square", }, { - 'source': MEDIUM_URL, - 'url': 'https://www.flickr.com/photos/someuser/3465234888/sizes/m/', - 'media': 'photo', - 'height': '500', - 'width': '333', - 'label': 'Medium', + "source": MEDIUM_URL, + "url": "https://www.flickr.com/photos/someuser/3465234888/sizes/m/", + "media": "photo", + "height": "500", + "width": "333", + "label": "Medium", }, ], - 'candownload': 1, - 'canprint': 0, + "candownload": 1, + "canprint": 0, }, } FLICKR_URL = re.compile( - r'https://api.flickr.com/services/rest/\?method=flickr.photos.getSizes&api_key=.*photo_id=.*&format=json&nojsoncallback=1' + r"https://api.flickr.com/services/rest/\?method=flickr.photos.getSizes&api_key=.*photo_id=.*&format=json&nojsoncallback=1" ) @@ -49,9 +49,9 @@ def test_get_sizes(self): responses.GET, FLICKR_URL, status=200, body=json.dumps(API_RESPONSE) ) - sizes = flickr.get_photo_sizes('1235123123') + sizes = flickr.get_photo_sizes("1235123123") - self.assertEqual(sizes, API_RESPONSE['sizes']['size']) + self.assertEqual(sizes, API_RESPONSE["sizes"]["size"]) def test_display_image(self): with responses.RequestsMock() as rsps: @@ -60,7 +60,7 @@ def test_display_image(self): ) disp_photo = flickr.get_display_photo( - 'https://farm9.staticflickr.com/8747/12346789012_bf1e234567_b.jpg' + "https://farm9.staticflickr.com/8747/12346789012_bf1e234567_b.jpg" ) self.assertEqual(disp_photo, MEDIUM_URL) diff --git a/mygpo/data/youtube.py b/mygpo/data/youtube.py index ecae71bf7..1bdb6d2be 100644 --- a/mygpo/data/youtube.py +++ b/mygpo/data/youtube.py @@ -16,13 +16,13 @@ def get_youtube_id(url): return None r = re.compile( - r'http://(?:[a-z]+\.)?youtube\.com/v/(.*)\.swf', re.IGNORECASE + r"http://(?:[a-z]+\.)?youtube\.com/v/(.*)\.swf", re.IGNORECASE ).match(url) if r is not None: return r.group(1) r = re.compile( - r'http://(?:[a-z]+\.)?youtube\.com/watch\?v=([^&]*)', re.IGNORECASE + r"http://(?:[a-z]+\.)?youtube\.com/watch\?v=([^&]*)", re.IGNORECASE ).match(url) if r is not None: return r.group(1) @@ -33,10 +33,10 @@ def get_youtube_id(url): def get_real_cover(url): rs = [ re.compile( - r'http://www\\.youtube\\.com/rss/user/([^/]+)/videos\\.rss', re.IGNORECASE + r"http://www\\.youtube\\.com/rss/user/([^/]+)/videos\\.rss", re.IGNORECASE ), re.compile( - r'http://www\\.youtube\\.com/profile_videos\\?user=([^\&]+)', re.IGNORECASE + r"http://www\\.youtube\\.com/profile_videos\\?user=([^\&]+)", re.IGNORECASE ), ] @@ -45,9 +45,9 @@ def get_real_cover(url): if m is None: continue username = m.group(1) - api_url = 'http://gdata.youtube.com/feeds/api/users/%s?v=2' % username + api_url = "http://gdata.youtube.com/feeds/api/users/%s?v=2" % username data = urllib.request.urlopen(api_url).read() - match = re.search('', data) + match = re.search("", data) if match is not None: return match.group(1) diff --git a/mygpo/decorators.py b/mygpo/decorators.py index e5687828d..2720523c9 100644 --- a/mygpo/decorators.py +++ b/mygpo/decorators.py @@ -52,14 +52,14 @@ def tmp(request, username, *args, **kwargs): User = get_user_model() user = get_object_or_404(User, username=username) token = user.profile.get_token(token_name) - u_token = request.GET.get('token', '') + u_token = request.GET.get("token", "") - if token == '' or token == u_token: + if token == "" or token == u_token: return fn(request, username, *args, **kwargs) else: if denied_template: - return render(request, denied_template, {'other_user': user}) + return render(request, denied_template, {"other_user": user}) else: return HttpResponseForbidden() @@ -84,7 +84,7 @@ def tmp(request, *args, **kwargs): def query_if_required(): - """ If required, queries some resource before calling the function + """If required, queries some resource before calling the function The decorated method is expected to be bound and its class is expected to have define the methods _needs_query() and _query(). @@ -104,14 +104,14 @@ def wrapper(self, *args, **kwargs): return decorator -def cors_origin(allowed_origin='*'): +def cors_origin(allowed_origin="*"): """ Adds an Access-Control-Allow-Origin header to the response """ def decorator(f): @wraps(f) def wrapper(*args, **kwargs): resp = f(*args, **kwargs) - resp['Access-Control-Allow-Origin'] = allowed_origin + resp["Access-Control-Allow-Origin"] = allowed_origin return resp return wrapper diff --git a/mygpo/directory/admin.py b/mygpo/directory/admin.py index 7b0382fcd..c2b433e98 100644 --- a/mygpo/directory/admin.py +++ b/mygpo/directory/admin.py @@ -8,9 +8,9 @@ class ExamplePodcastAdmin(admin.ModelAdmin): """ Admin page for example podcasts """ # configuration for the list view - list_display = ('podcast',) + list_display = ("podcast",) # fetch the related objects for the fields in list_display - list_select_related = ('podcast',) + list_select_related = ("podcast",) - raw_id_fields = ('podcast',) + raw_id_fields = ("podcast",) diff --git a/mygpo/directory/management/commands/category-merge-spellings.py b/mygpo/directory/management/commands/category-merge-spellings.py index 00d2cdc61..b3ca3728a 100644 --- a/mygpo/directory/management/commands/category-merge-spellings.py +++ b/mygpo/directory/management/commands/category-merge-spellings.py @@ -26,13 +26,13 @@ def handle(self, *args, **options): print("Adding new spellings for %s ..." % cat_name) category, created = Category.objects.get_or_create( - tags__tag=slugify(cat_name), defaults={'title': cat_name} + tags__tag=slugify(cat_name), defaults={"title": cat_name} ) for spelling in spellings: tag, created = CategoryTag.objects.get_or_create( - tag=spelling, defaults={'category': category} + tag=spelling, defaults={"category": category} ) if created: diff --git a/mygpo/directory/management/commands/update-toplist.py b/mygpo/directory/management/commands/update-toplist.py index bd4b2b91b..d1441d521 100644 --- a/mygpo/directory/management/commands/update-toplist.py +++ b/mygpo/directory/management/commands/update-toplist.py @@ -12,19 +12,19 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( - '--silent', - action='store_true', - dest='silent', + "--silent", + action="store_true", + dest="silent", default=False, help="Don't show any output", ), def handle(self, *args, **options): - silent = options.get('silent') + silent = options.get("silent") podcasts = Podcast.objects.all() - total = podcasts.count_fast() + total = podcasts.count() for n, podcast in enumerate(podcasts): update_podcast_subscribers.delay(podcast.get_id()) diff --git a/mygpo/directory/migrations/0001_initial.py b/mygpo/directory/migrations/0001_initial.py index 6a22dd0d5..0c74b0224 100644 --- a/mygpo/directory/migrations/0001_initial.py +++ b/mygpo/directory/migrations/0001_initial.py @@ -6,33 +6,33 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0028_episode_indexes')] + dependencies = [("podcasts", "0028_episode_indexes")] operations = [ migrations.CreateModel( - name='ExamplePodcast', + name="ExamplePodcast", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('order', models.PositiveSmallIntegerField()), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), + ("order", models.PositiveSmallIntegerField()), ( - 'podcast', - models.ForeignKey(to='podcasts.Podcast', on_delete=models.CASCADE), + "podcast", + models.ForeignKey(to="podcasts.Podcast", on_delete=models.CASCADE), ), ], - options={'ordering': ['order'], 'abstract': False}, + options={"ordering": ["order"], "abstract": False}, bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='examplepodcast', unique_together=set([('order',)]) + name="examplepodcast", unique_together=set([("order",)]) ), ] diff --git a/mygpo/directory/models.py b/mygpo/directory/models.py index 1d29566e1..480d0515b 100644 --- a/mygpo/directory/models.py +++ b/mygpo/directory/models.py @@ -10,7 +10,7 @@ class ExamplePodcastsManager(models.Manager): def get_podcasts(self): """ The example podcasts """ return Podcast.objects.filter(examplepodcast__isnull=False).order_by( - 'examplepodcast__order' + "examplepodcast__order" ) @@ -22,4 +22,4 @@ class ExamplePodcast(UpdateInfoModel, OrderedModel): objects = ExamplePodcastsManager() class Meta(OrderedModel.Meta): - unique_together = [('order',)] + unique_together = [("order",)] diff --git a/mygpo/directory/tags.py b/mygpo/directory/tags.py index 4d61cc799..ddfc920a1 100644 --- a/mygpo/directory/tags.py +++ b/mygpo/directory/tags.py @@ -22,8 +22,8 @@ def _query(self): categories = list( Category.objects.filter(num_entries__gt=0) .filter(tags__isnull=False) - .order_by('-modified')[: self.total] - .prefetch_related('tags') + .order_by("-modified")[: self.total] + .prefetch_related("tags") ) self._categories = categories[: self.num_cat] self._tagcloud = sorted( @@ -59,7 +59,7 @@ def update_category(podcast): try: category, created = Category.objects.get_or_create( - tags__tag=slugify(random_tag), defaults={'title': random_tag} + tags__tag=slugify(random_tag), defaults={"title": random_tag} ) except IntegrityError as ie: @@ -67,7 +67,7 @@ def update_category(podcast): # the exception message should be like: # IntegrityError: duplicate key value violates unique # constraint "categories_category_title_key" - if 'categories_category_title_key' not in str(ie): + if "categories_category_title_key" not in str(ie): raise category = Category.objects.get(title=random_tag) diff --git a/mygpo/directory/tasks.py b/mygpo/directory/tasks.py index 3d4b5e921..c57ba85af 100644 --- a/mygpo/directory/tasks.py +++ b/mygpo/directory/tasks.py @@ -16,8 +16,8 @@ def update_podcast_subscribers(podcast_id): # calculate current number of subscribers podcast.subscribers = ( Subscription.objects.filter(podcast=podcast) - .order_by('user') - .distinct('user') + .order_by("user") + .distinct("user") .count() ) podcast.save() diff --git a/mygpo/directory/tests.py b/mygpo/directory/tests.py index d136ef1d2..0fc61d301 100644 --- a/mygpo/directory/tests.py +++ b/mygpo/directory/tests.py @@ -14,7 +14,7 @@ class ToplistTests(unittest.TestCase): def test_toplist_languages(self): """ Test the all_languages method of the toplists """ - languages = ['de', 'de_AT', 'en'] + languages = ["de", "de_AT", "en"] for lang in languages: Podcast.objects.create( id=uuid.uuid1(), created=datetime.utcnow(), language=lang @@ -22,4 +22,4 @@ def test_toplist_languages(self): view = ToplistView() all_langs = view.all_languages() - self.assertEqual(all_langs, {'de': 'Deutsch', 'en': 'English'}) + self.assertEqual(all_langs, {"de": "Deutsch", "en": "English"}) diff --git a/mygpo/directory/urls.py b/mygpo/directory/urls.py index cb5f5e8bd..8cb89abf2 100644 --- a/mygpo/directory/urls.py +++ b/mygpo/directory/urls.py @@ -4,26 +4,26 @@ urlpatterns = [ - path('toplist/', views.PodcastToplistView.as_view(), name='toplist'), + path("toplist/", views.PodcastToplistView.as_view(), name="toplist"), path( - 'toplist/episodes', views.EpisodeToplistView.as_view(), name='episode-toplist' + "toplist/episodes", views.EpisodeToplistView.as_view(), name="episode-toplist" ), - path('directory/', views.Directory.as_view(), name='directory-home'), - path('carousel/', views.Carousel.as_view(), name='carousel-demo'), - path('missing/', views.MissingPodcast.as_view(), name='missing-podcast'), - path('add-podcast/', views.AddPodcast.as_view(), name='add-podcast'), + path("directory/", views.Directory.as_view(), name="directory-home"), + path("carousel/", views.Carousel.as_view(), name="carousel-demo"), + path("missing/", views.MissingPodcast.as_view(), name="missing-podcast"), + path("add-podcast/", views.AddPodcast.as_view(), name="add-podcast"), path( - 'add-podcast/', + "add-podcast/", views.AddPodcastStatus.as_view(), - name='add-podcast-status', + name="add-podcast-status", ), - path('directory/+license', views.LicenseList.as_view(), name='license-podcasts'), + path("directory/+license", views.LicenseList.as_view(), name="license-podcasts"), path( - 'directory/+license/+url/', + "directory/+license/+url/", views.LicensePodcastList.as_view(), - name='license-podcasts-url', + name="license-podcasts-url", ), - path('directory/', views.category, name='directory'), - path('search/', views.search, name='search'), - path('lists/', views.podcast_lists, name='podcast-lists'), + path("directory/", views.category, name="directory"), + path("search/", views.search, name="search"), + path("lists/", views.podcast_lists, name="podcast-lists"), ] diff --git a/mygpo/directory/views.py b/mygpo/directory/views.py index 4bc8127af..bfc469785 100644 --- a/mygpo/directory/views.py +++ b/mygpo/directory/views.py @@ -48,15 +48,15 @@ def dispatch(self, *args, **kwargs): return super(ToplistView, self).dispatch(*args, **kwargs) def all_languages(self): - """ Returns all 2-letter language codes that are used by podcasts. + """Returns all 2-letter language codes that are used by podcasts. It filters obviously invalid strings, but does not check if any - of these codes is contained in ISO 639. """ + of these codes is contained in ISO 639.""" query = Podcast.objects.exclude(language__isnull=True) - query = query.distinct('language').values('language') + query = query.distinct("language").values("language") - langs = [o['language'] for o in query] + langs = [o["language"] for o in query] langs = sorted(sanitize_language_codes(langs)) return get_language_names(langs) @@ -73,19 +73,19 @@ def site(self): class PodcastToplistView(ToplistView): """ Most subscribed podcasts """ - template_name = 'toplist.html' + template_name = "toplist.html" def get_context_data(self, num=100): context = super(PodcastToplistView, self).get_context_data() entries = ( Podcast.objects.all() - .prefetch_related('slugs') + .prefetch_related("slugs") .toplist(self.language())[:num] ) - context['entries'] = entries + context["entries"] = entries - context['max_subscribers'] = max([0] + [p.subscriber_count() for p in entries]) + context["max_subscribers"] = max([0] + [p.subscriber_count() for p in entries]) return context @@ -93,23 +93,23 @@ def get_context_data(self, num=100): class EpisodeToplistView(ToplistView): """ Most listened-to episodes """ - template_name = 'episode_toplist.html' + template_name = "episode_toplist.html" def get_context_data(self, num=100): context = super(EpisodeToplistView, self).get_context_data() entries = ( Episode.objects.all() - .select_related('podcast') - .prefetch_related('slugs', 'podcast__slugs') + .select_related("podcast") + .prefetch_related("slugs", "podcast__slugs") .toplist(self.language())[:num] ) - context['entries'] = entries + context["entries"] = entries # Determine maximum listener amount (or 0 if no entries exist) listeners = [e.listeners for e in entries if e.listeners is not None] max_listeners = max(listeners, default=0) - context['max_listeners'] = max_listeners + context["max_listeners"] = max_listeners return context @@ -123,10 +123,10 @@ def get(self, request): return render( request, - 'carousel.html', + "carousel.html", { # evaluated lazyly, cached by template - 'topics': Topics() + "topics": Topics() }, ) @@ -140,18 +140,18 @@ def get(self, request): return render( request, - 'directory.html', + "directory.html", { # evaluated lazyly, cached by template - 'topics': Topics(), - 'podcastlists': self.get_random_list(), - 'random_podcast': Podcast.objects.all().random().first(), - 'podcast_ad': Podcast.objects.get_advertised_podcast(), + "topics": Topics(), + "podcastlists": self.get_random_list(), + "random_podcast": Podcast.objects.all().random().first(), + "podcast_ad": Podcast.objects.get_advertised_podcast(), }, ) def get_random_list(self, podcasts_per_list=5): - random_list = PodcastList.objects.order_by('?').first() + random_list = PodcastList.objects.order_by("?").first() yield random_list @@ -163,11 +163,11 @@ def category(request, category, page_size=20): except Category.DoesNotExist: return HttpResponseNotFound() - podcasts = category.entries.all().prefetch_related('podcast', 'podcast__slugs') + podcasts = category.entries.all().prefetch_related("podcast", "podcast__slugs") paginator = Paginator(podcasts, page_size) - page = request.GET.get('page') + page = request.GET.get("page") try: podcasts = paginator.page(page) except PageNotAnInteger: @@ -181,8 +181,8 @@ def category(request, category, page_size=20): return render( request, - 'category.html', - {'entries': podcasts, 'category': category.title, 'page_list': page_list}, + "category.html", + {"entries": podcasts, "category": category.title, "page_list": page_list}, ) @@ -191,13 +191,13 @@ def category(request, category, page_size=20): @cache_control(private=True) @vary_on_cookie -def search(request, template='search.html', args={}): +def search(request, template="search.html", args={}): - if 'q' in request.GET: - q = request.GET.get('q', '') + if "q" in request.GET: + q = request.GET.get("q", "") try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 @@ -238,13 +238,13 @@ def podcast_lists(request, page_size=20): lists = ( PodcastList.objects.all() - .annotate(num_votes=Count('votes')) - .order_by('-num_votes') + .annotate(num_votes=Count("votes")) + .order_by("-num_votes") ) paginator = Paginator(lists, page_size) - page = request.GET.get('page') + page = request.GET.get("page") try: lists = paginator.page(page) except PageNotAnInteger: @@ -258,7 +258,7 @@ def podcast_lists(request, page_size=20): page_list = get_page_list(1, num_pages, int(page), 15) return render( - request, 'podcast_lists.html', {'lists': lists, 'page_list': page_list} + request, "podcast_lists.html", {"lists": lists, "page_list": page_list} ) @@ -271,7 +271,7 @@ def get(self, request): site = RequestSite(request) # check if we're doing a query - url = request.GET.get('q', None) + url = request.GET.get("q", None) if not url: podcast = None @@ -294,8 +294,8 @@ def get(self, request): return render( request, - 'missing.html', - {'site': site, 'q': url, 'podcast': podcast, 'can_add': can_add}, + "missing.html", + {"site": site, "q": url, "podcast": podcast, "can_add": can_add}, ) @@ -307,44 +307,44 @@ class AddPodcast(View): @method_decorator(vary_on_cookie) def post(self, request): - url = request.POST.get('url', None) + url = request.POST.get("url", None) if not url: raise Http404 res = update_podcasts.delay([url]) - return HttpResponseRedirect(reverse('add-podcast-status', args=[res.task_id])) + return HttpResponseRedirect(reverse("add-podcast-status", args=[res.task_id])) class AddPodcastStatus(TemplateView): """ Status of adding a podcast """ - template_name = 'directory/add-podcast-status.html' + template_name = "directory/add-podcast-status.html" def get(self, request, task_id): result = update_podcasts.AsyncResult(task_id) if not result.ready(): - return self.render_to_response({'ready': False}) + return self.render_to_response({"ready": False}) try: podcast_ids = result.get() podcasts = Podcast.objects.filter(pk__in=podcast_ids) - messages.success(request, _('%d podcasts added' % len(podcasts))) + messages.success(request, _("%d podcasts added" % len(podcasts))) except (UpdatePodcastException, NoEpisodesException) as ex: messages.error(request, str(ex)) podcast = None - return self.render_to_response({'ready': True, 'podcasts': podcasts}) + return self.render_to_response({"ready": True, "podcasts": podcasts}) class PodcastListView(ListView): """ A generic podcast list view """ paginate_by = 15 - context_object_name = 'podcasts' + context_object_name = "podcasts" @method_decorator(vary_on_cookie) @method_decorator(cache_control(private=True)) @@ -354,13 +354,13 @@ def dispatch(self, *args, **kwargs): @property def _page(self): - """ The current page + """The current page There seems to be no other pre-defined method for getting the current page, see https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin """ - return self.get_context_data()['page_obj'] + return self.get_context_data()["page_obj"] def page_list(self, page_size=15): """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """ @@ -379,25 +379,25 @@ def max_subscribers(self): class LicensePodcastList(PodcastListView): """ Lists podcasts with a given license """ - template_name = 'directory/license-podcasts.html' + template_name = "directory/license-podcasts.html" def get_queryset(self): return Podcast.objects.all().license(self.license_url) @property def license_url(self): - return self.kwargs['license_url'] + return self.kwargs["license_url"] class LicenseList(TemplateView): """ Lists all podcast licenses """ - template_name = 'directory/licenses.html' + template_name = "directory/licenses.html" def licenses(self): """ Returns all podcast licenses """ query = Podcast.objects.exclude(license__isnull=True) values = query.values("license").annotate(Count("id")).order_by() - counter = Counter({l['license']: l['id__count'] for l in values}) + counter = Counter({l["license"]: l["id__count"] for l in values}) return counter.most_common() diff --git a/mygpo/episodestates/__init__.py b/mygpo/episodestates/__init__.py index a63b5e9aa..13c3e093a 100644 --- a/mygpo/episodestates/__init__.py +++ b/mygpo/episodestates/__init__.py @@ -1 +1 @@ -default_app_config = 'mygpo.episodestates.apps.EpisodeStatesConfig' +default_app_config = "mygpo.episodestates.apps.EpisodeStatesConfig" diff --git a/mygpo/episodestates/admin.py b/mygpo/episodestates/admin.py index 4fdccffd1..73d0afea8 100644 --- a/mygpo/episodestates/admin.py +++ b/mygpo/episodestates/admin.py @@ -8,11 +8,11 @@ class EpisodeStateAdmin(admin.ModelAdmin): """ Admin page for subscriptions """ # configuration for the list view - list_display = ('user', 'episode', 'action') + list_display = ("user", "episode", "action") # fetch the related objects for the fields in list_display - list_select_related = ('user', 'episode') + list_select_related = ("user", "episode") - raw_id_fields = ('user', 'episode') + raw_id_fields = ("user", "episode") show_full_result_count = False diff --git a/mygpo/episodestates/apps.py b/mygpo/episodestates/apps.py index cdb77f13d..4d592ce93 100644 --- a/mygpo/episodestates/apps.py +++ b/mygpo/episodestates/apps.py @@ -7,7 +7,7 @@ def set_episode_state(sender, **kwargs): from mygpo.episodestates.tasks import update_episode_state - historyentry = kwargs.get('instance', None) + historyentry = kwargs.get("instance", None) if not historyentry: return @@ -16,9 +16,9 @@ def set_episode_state(sender, **kwargs): class EpisodeStatesConfig(AppConfig): - name = 'mygpo.episodestates' - verbose_name = 'Episode States' + name = "mygpo.episodestates" + verbose_name = "Episode States" def ready(self): - EpisodeHistoryEntry = apps.get_model('history.EpisodeHistoryEntry') + EpisodeHistoryEntry = apps.get_model("history.EpisodeHistoryEntry") post_save.connect(set_episode_state, sender=EpisodeHistoryEntry) diff --git a/mygpo/episodestates/migrations/0001_initial.py b/mygpo/episodestates/migrations/0001_initial.py index 93763af36..3dcba364f 100644 --- a/mygpo/episodestates/migrations/0001_initial.py +++ b/mygpo/episodestates/migrations/0001_initial.py @@ -8,43 +8,43 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0029_episode_index_toplist'), + ("podcasts", "0029_episode_index_toplist"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='EpisodeState', + name="EpisodeState", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ( - 'action', + "action", models.CharField( max_length=8, choices=[ - ('download', 'downloaded'), - ('play', 'played'), - ('delete', 'deleted'), - ('new', 'marked as new'), - ('flattr', "flattr'd"), + ("download", "downloaded"), + ("play", "played"), + ("delete", "deleted"), + ("new", "marked as new"), + ("flattr", "flattr'd"), ], ), ), - ('timestamp', models.DateTimeField()), + ("timestamp", models.DateTimeField()), ( - 'episode', - models.ForeignKey(to='podcasts.Episode', on_delete=models.CASCADE), + "episode", + models.ForeignKey(to="podcasts.Episode", on_delete=models.CASCADE), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -54,6 +54,6 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='episodestate', unique_together=set([('user', 'episode')]) + name="episodestate", unique_together=set([("user", "episode")]) ), ] diff --git a/mygpo/episodestates/models.py b/mygpo/episodestates/models.py index 1448ab9e0..110e7f095 100644 --- a/mygpo/episodestates/models.py +++ b/mygpo/episodestates/models.py @@ -24,7 +24,7 @@ class EpisodeState(models.Model): timestamp = models.DateTimeField() class Meta: - unique_together = [('user', 'episode')] + unique_together = [("user", "episode")] @classmethod def dict_for_user(cls, user, episodes=None): @@ -34,4 +34,4 @@ def dict_for_user(cls, user, episodes=None): if episodes is not None: states = states.filter(episode__in=episodes) - return dict(states.values_list('episode', 'action')) + return dict(states.values_list("episode", "action")) diff --git a/mygpo/episodestates/tasks.py b/mygpo/episodestates/tasks.py index 1d2afe295..718a27acd 100644 --- a/mygpo/episodestates/tasks.py +++ b/mygpo/episodestates/tasks.py @@ -25,7 +25,7 @@ def update_episode_state(historyentry_pk): episode = historyentry.episode logger.info( - 'Updating Episode State for {user} / {episode}'.format( + "Updating Episode State for {user} / {episode}".format( user=user, episode=episode ) ) @@ -33,5 +33,5 @@ def update_episode_state(historyentry_pk): state = EpisodeState.objects.update_or_create( user=user, episode=episode, - defaults={'action': historyentry.action, 'timestamp': historyentry.timestamp}, + defaults={"action": historyentry.action, "timestamp": historyentry.timestamp}, ) diff --git a/mygpo/favorites/admin.py b/mygpo/favorites/admin.py index b391d83ed..471adb835 100644 --- a/mygpo/favorites/admin.py +++ b/mygpo/favorites/admin.py @@ -8,11 +8,11 @@ class FavoriteEpisodeAdmin(admin.ModelAdmin): """ Admin page for favorite episodes """ # configuration for the list view - list_display = ('user', 'episode') + list_display = ("user", "episode") # fetch the related objects for the fields in list_display - list_select_related = ('user', 'episode') + list_select_related = ("user", "episode") - raw_id_fields = ('user', 'episode') + raw_id_fields = ("user", "episode") show_full_result_count = False diff --git a/mygpo/favorites/migrations/0001_initial.py b/mygpo/favorites/migrations/0001_initial.py index 11e3b4eb4..440fcc0ef 100644 --- a/mygpo/favorites/migrations/0001_initial.py +++ b/mygpo/favorites/migrations/0001_initial.py @@ -10,39 +10,39 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('podcasts', '0023_auto_20140729_1711'), + ("podcasts", "0023_auto_20140729_1711"), ] operations = [ migrations.CreateModel( - name='FavoriteEpisode', + name="FavoriteEpisode", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), ( - 'episode', + "episode", models.ForeignKey( - to='podcasts.Episode', + to="podcasts.Episode", on_delete=django.db.models.deletion.PROTECT, ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), ), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ) ] diff --git a/mygpo/favorites/migrations/0002_unique.py b/mygpo/favorites/migrations/0002_unique.py index 9865b89cc..96d894aa4 100644 --- a/mygpo/favorites/migrations/0002_unique.py +++ b/mygpo/favorites/migrations/0002_unique.py @@ -6,10 +6,10 @@ class Migration(migrations.Migration): - dependencies = [('favorites', '0001_initial')] + dependencies = [("favorites", "0001_initial")] operations = [ migrations.AlterUniqueTogether( - name='favoriteepisode', unique_together=set([('user', 'episode')]) + name="favoriteepisode", unique_together=set([("user", "episode")]) ) ] diff --git a/mygpo/favorites/models.py b/mygpo/favorites/models.py index c87d597d7..3fed481a6 100644 --- a/mygpo/favorites/models.py +++ b/mygpo/favorites/models.py @@ -14,15 +14,15 @@ class FavoriteEpisode(UpdateInfoModel): episode = models.ForeignKey(Episode, db_index=True, on_delete=models.PROTECT) class Meta: - unique_together = [('user', 'episode')] + unique_together = [("user", "episode")] @classmethod def episodes_for_user(cls, user): - """ Favorite episodes of the given user + """Favorite episodes of the given user - Returns the episodes directly, not the FavoriteEpisode objects """ + Returns the episodes directly, not the FavoriteEpisode objects""" return ( Episode.objects.filter(favoriteepisode__user=user) - .select_related('podcast') - .prefetch_related('slugs', 'podcast__slugs') + .select_related("podcast") + .prefetch_related("slugs", "podcast__slugs") ) diff --git a/mygpo/history/admin.py b/mygpo/history/admin.py index 76d7be816..9d0c9d7f7 100644 --- a/mygpo/history/admin.py +++ b/mygpo/history/admin.py @@ -8,12 +8,12 @@ class HistoryEntryAdmin(admin.ModelAdmin): """ Admin page for history entries """ # configuration for the list view - list_display = ('user', 'timestamp', 'podcast', 'action', 'client') + list_display = ("user", "timestamp", "podcast", "action", "client") # fetch the related objects for the fields in list_display - list_select_related = ('user', 'podcast', 'client') + list_select_related = ("user", "podcast", "client") - raw_id_fields = ('user', 'podcast', 'client') + raw_id_fields = ("user", "podcast", "client") show_full_result_count = False @@ -23,11 +23,11 @@ class EpisodeHistoryEntryAdmin(admin.ModelAdmin): """ Admin page for episode history entries """ # configuration for the list view - list_display = ('user', 'timestamp', 'episode', 'action', 'client') + list_display = ("user", "timestamp", "episode", "action", "client") # fetch the related objects for the fields in list_display - list_select_related = ('user', 'episode', 'client') + list_select_related = ("user", "episode", "client") - raw_id_fields = ('user', 'episode', 'client') + raw_id_fields = ("user", "episode", "client") show_full_result_count = False diff --git a/mygpo/history/migrations/0001_initial.py b/mygpo/history/migrations/0001_initial.py index 0664f694d..54e2c1e07 100644 --- a/mygpo/history/migrations/0001_initial.py +++ b/mygpo/history/migrations/0001_initial.py @@ -8,55 +8,55 @@ class Migration(migrations.Migration): dependencies = [ - ('users', '0007_syncgroup_protect'), + ("users", "0007_syncgroup_protect"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('podcasts', '0023_auto_20140729_1711'), + ("podcasts", "0023_auto_20140729_1711"), ] operations = [ migrations.CreateModel( - name='HistoryEntry', + name="HistoryEntry", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('timestamp', models.DateTimeField()), + ("timestamp", models.DateTimeField()), ( - 'action', + "action", models.CharField( max_length=11, choices=[ - ('subscribe', 'subscribed'), - ('unsubscribe', 'unsubscribed'), + ("subscribe", "subscribed"), + ("unsubscribe", "unsubscribed"), ], ), ), ( - 'client', - models.ForeignKey(to='users.Client', on_delete=models.CASCADE), + "client", + models.ForeignKey(to="users.Client", on_delete=models.CASCADE), ), ( - 'podcast', - models.ForeignKey(to='podcasts.Podcast', on_delete=models.CASCADE), + "podcast", + models.ForeignKey(to="podcasts.Podcast", on_delete=models.CASCADE), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), ), ], - options={'ordering': ['timestamp']}, + options={"ordering": ["timestamp"]}, bases=(models.Model,), ), migrations.AlterIndexTogether( - name='historyentry', - index_together=set([('user', 'podcast'), ('user', 'client')]), + name="historyentry", + index_together=set([("user", "podcast"), ("user", "client")]), ), ] diff --git a/mygpo/history/migrations/0002_pluralname.py b/mygpo/history/migrations/0002_pluralname.py index a0419160d..66337eb71 100644 --- a/mygpo/history/migrations/0002_pluralname.py +++ b/mygpo/history/migrations/0002_pluralname.py @@ -6,14 +6,14 @@ class Migration(migrations.Migration): - dependencies = [('history', '0001_initial')] + dependencies = [("history", "0001_initial")] operations = [ migrations.AlterModelOptions( - name='historyentry', + name="historyentry", options={ - 'ordering': ['timestamp'], - 'verbose_name_plural': 'History Entries', + "ordering": ["timestamp"], + "verbose_name_plural": "History Entries", }, ) ] diff --git a/mygpo/history/migrations/0003_historyentry.py b/mygpo/history/migrations/0003_historyentry.py index 44bcbb188..bc28389d6 100644 --- a/mygpo/history/migrations/0003_historyentry.py +++ b/mygpo/history/migrations/0003_historyentry.py @@ -6,33 +6,33 @@ class Migration(migrations.Migration): - dependencies = [('history', '0002_pluralname')] + dependencies = [("history", "0002_pluralname")] operations = [ migrations.AlterModelOptions( - name='historyentry', + name="historyentry", options={ - 'ordering': ['-timestamp'], - 'verbose_name_plural': 'History Entries', + "ordering": ["-timestamp"], + "verbose_name_plural": "History Entries", }, ), migrations.AlterField( - model_name='historyentry', - name='action', + model_name="historyentry", + name="action", field=models.CharField( max_length=11, choices=[ - ('subscribe', 'subscribed'), - ('unsubscribe', 'unsubscribed'), - ('flattr', "flattr'd"), + ("subscribe", "subscribed"), + ("unsubscribe", "unsubscribed"), + ("flattr", "flattr'd"), ], ), ), migrations.AlterField( - model_name='historyentry', - name='client', + model_name="historyentry", + name="client", field=models.ForeignKey( - to='users.Client', null=True, on_delete=models.CASCADE + to="users.Client", null=True, on_delete=models.CASCADE ), ), ] diff --git a/mygpo/history/migrations/0004_historyentry_episode.py b/mygpo/history/migrations/0004_historyentry_episode.py index 85c7ffc44..df0999e31 100644 --- a/mygpo/history/migrations/0004_historyentry_episode.py +++ b/mygpo/history/migrations/0004_historyentry_episode.py @@ -6,14 +6,14 @@ class Migration(migrations.Migration): - dependencies = [('history', '0003_historyentry')] + dependencies = [("history", "0003_historyentry")] operations = [ migrations.AddField( - model_name='historyentry', - name='episode', + model_name="historyentry", + name="episode", field=models.ForeignKey( - to='podcasts.Episode', null=True, on_delete=models.CASCADE + to="podcasts.Episode", null=True, on_delete=models.CASCADE ), preserve_default=True, ) diff --git a/mygpo/history/migrations/0005_episodehistoryentry.py b/mygpo/history/migrations/0005_episodehistoryentry.py index dce6aad07..c2b9e85db 100644 --- a/mygpo/history/migrations/0005_episodehistoryentry.py +++ b/mygpo/history/migrations/0005_episodehistoryentry.py @@ -8,73 +8,73 @@ class Migration(migrations.Migration): dependencies = [ - ('users', '0007_syncgroup_protect'), + ("users", "0007_syncgroup_protect"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('podcasts', '0023_auto_20140729_1711'), - ('history', '0004_historyentry_episode'), + ("podcasts", "0023_auto_20140729_1711"), + ("history", "0004_historyentry_episode"), ] operations = [ migrations.CreateModel( - name='EpisodeHistoryEntry', + name="EpisodeHistoryEntry", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('timestamp', models.DateTimeField()), - ('created', models.DateTimeField()), + ("timestamp", models.DateTimeField()), + ("created", models.DateTimeField()), ( - 'action', + "action", models.CharField( max_length=8, choices=[ - ('download', 'downloaded'), - ('play', 'played'), - ('delete', 'deleted'), - ('new', 'marked as new'), - ('flattr', "flattr'd"), + ("download", "downloaded"), + ("play", "played"), + ("delete", "deleted"), + ("new", "marked as new"), + ("flattr", "flattr'd"), ], ), ), - ('podcast_ref_url', models.URLField(max_length=2048, null=True)), - ('episode_ref_url', models.URLField(max_length=2048, null=True)), - ('started', models.IntegerField(null=True)), - ('stopped', models.IntegerField(null=True)), - ('total', models.IntegerField(null=True)), + ("podcast_ref_url", models.URLField(max_length=2048, null=True)), + ("episode_ref_url", models.URLField(max_length=2048, null=True)), + ("started", models.IntegerField(null=True)), + ("stopped", models.IntegerField(null=True)), + ("total", models.IntegerField(null=True)), ( - 'client', + "client", models.ForeignKey( - to='users.Client', null=True, on_delete=models.CASCADE + to="users.Client", null=True, on_delete=models.CASCADE ), ), ( - 'episode', + "episode", models.ForeignKey( - to='podcasts.Episode', null=True, on_delete=models.CASCADE + to="podcasts.Episode", null=True, on_delete=models.CASCADE ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), ), ], options={ - 'ordering': ['-timestamp'], - 'verbose_name_plural': 'Episode History Entries', + "ordering": ["-timestamp"], + "verbose_name_plural": "Episode History Entries", }, bases=(models.Model,), ), migrations.AlterIndexTogether( - name='episodehistoryentry', - index_together=set([('user', 'client', 'episode', 'action', 'timestamp')]), + name="episodehistoryentry", + index_together=set([("user", "client", "episode", "action", "timestamp")]), ), - migrations.RemoveField(model_name='historyentry', name='episode'), + migrations.RemoveField(model_name="historyentry", name="episode"), ] diff --git a/mygpo/history/migrations/0006_episodehistory_index.py b/mygpo/history/migrations/0006_episodehistory_index.py index 256cbf205..097063985 100644 --- a/mygpo/history/migrations/0006_episodehistory_index.py +++ b/mygpo/history/migrations/0006_episodehistory_index.py @@ -6,15 +6,15 @@ class Migration(migrations.Migration): - dependencies = [('history', '0005_episodehistoryentry')] + dependencies = [("history", "0005_episodehistoryentry")] operations = [ migrations.AlterIndexTogether( - name='episodehistoryentry', + name="episodehistoryentry", index_together=set( [ - ('user', 'action', 'episode'), - ('user', 'client', 'episode', 'action', 'timestamp'), + ("user", "action", "episode"), + ("user", "client", "episode", "action", "timestamp"), ] ), ) diff --git a/mygpo/history/migrations/0007_episodehistory_index.py b/mygpo/history/migrations/0007_episodehistory_index.py index 308fc7c94..b8c39d0a2 100644 --- a/mygpo/history/migrations/0007_episodehistory_index.py +++ b/mygpo/history/migrations/0007_episodehistory_index.py @@ -6,16 +6,16 @@ class Migration(migrations.Migration): - dependencies = [('history', '0006_episodehistory_index')] + dependencies = [("history", "0006_episodehistory_index")] operations = [ migrations.AlterIndexTogether( - name='episodehistoryentry', + name="episodehistoryentry", index_together=set( [ - ('user', 'action', 'episode'), - ('user', 'client', 'episode', 'action', 'timestamp'), - ('user', 'episode', 'timestamp'), + ("user", "action", "episode"), + ("user", "client", "episode", "action", "timestamp"), + ("user", "episode", "timestamp"), ] ), ) diff --git a/mygpo/history/migrations/0008_episodehistory_index.py b/mygpo/history/migrations/0008_episodehistory_index.py index 24ce31b7f..40168c40d 100644 --- a/mygpo/history/migrations/0008_episodehistory_index.py +++ b/mygpo/history/migrations/0008_episodehistory_index.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('history', '0007_episodehistory_index')] + dependencies = [("history", "0007_episodehistory_index")] operations = [ migrations.AlterIndexTogether( - name='episodehistoryentry', + name="episodehistoryentry", index_together=set( [ - ('user', 'action', 'episode'), - ('user', 'client', 'episode', 'action', 'timestamp'), - ('episode', 'timestamp'), - ('user', 'episode', 'timestamp'), + ("user", "action", "episode"), + ("user", "client", "episode", "action", "timestamp"), + ("episode", "timestamp"), + ("user", "episode", "timestamp"), ] ), ) diff --git a/mygpo/history/migrations/0009_blank_fields.py b/mygpo/history/migrations/0009_blank_fields.py index c1195ee10..fa9b89b68 100644 --- a/mygpo/history/migrations/0009_blank_fields.py +++ b/mygpo/history/migrations/0009_blank_fields.py @@ -6,50 +6,50 @@ class Migration(migrations.Migration): - dependencies = [('history', '0008_episodehistory_index')] + dependencies = [("history", "0008_episodehistory_index")] operations = [ migrations.AlterField( - model_name='episodehistoryentry', - name='client', + model_name="episodehistoryentry", + name="client", field=models.ForeignKey( - blank=True, to='users.Client', null=True, on_delete=models.CASCADE + blank=True, to="users.Client", null=True, on_delete=models.CASCADE ), preserve_default=True, ), migrations.AlterField( - model_name='episodehistoryentry', - name='created', + model_name="episodehistoryentry", + name="created", field=models.DateTimeField(auto_now_add=True), preserve_default=True, ), migrations.AlterField( - model_name='episodehistoryentry', - name='episode_ref_url', + model_name="episodehistoryentry", + name="episode_ref_url", field=models.URLField(max_length=2048, null=True, blank=True), preserve_default=True, ), migrations.AlterField( - model_name='episodehistoryentry', - name='podcast_ref_url', + model_name="episodehistoryentry", + name="podcast_ref_url", field=models.URLField(max_length=2048, null=True, blank=True), preserve_default=True, ), migrations.AlterField( - model_name='episodehistoryentry', - name='started', + model_name="episodehistoryentry", + name="started", field=models.IntegerField(null=True, blank=True), preserve_default=True, ), migrations.AlterField( - model_name='episodehistoryentry', - name='stopped', + model_name="episodehistoryentry", + name="stopped", field=models.IntegerField(null=True, blank=True), preserve_default=True, ), migrations.AlterField( - model_name='episodehistoryentry', - name='total', + model_name="episodehistoryentry", + name="total", field=models.IntegerField(null=True, blank=True), preserve_default=True, ), diff --git a/mygpo/history/migrations/0010_episode_history_index.py b/mygpo/history/migrations/0010_episode_history_index.py index 10c05d4ad..be552ecad 100644 --- a/mygpo/history/migrations/0010_episode_history_index.py +++ b/mygpo/history/migrations/0010_episode_history_index.py @@ -4,18 +4,18 @@ class Migration(migrations.Migration): - dependencies = [('history', '0009_blank_fields')] + dependencies = [("history", "0009_blank_fields")] operations = [ migrations.AlterIndexTogether( - name='episodehistoryentry', + name="episodehistoryentry", index_together=set( [ - ('user', 'action', 'episode'), - ('user', 'timestamp'), - ('user', 'client', 'episode', 'action', 'timestamp'), - ('episode', 'timestamp'), - ('user', 'episode', 'timestamp'), + ("user", "action", "episode"), + ("user", "timestamp"), + ("user", "client", "episode", "action", "timestamp"), + ("episode", "timestamp"), + ("user", "episode", "timestamp"), ] ), ) diff --git a/mygpo/history/models.py b/mygpo/history/models.py index d106da166..b9548eadf 100644 --- a/mygpo/history/models.py +++ b/mygpo/history/models.py @@ -16,13 +16,13 @@ class HistoryEntry(models.Model): """ A entry in the history """ - SUBSCRIBE = 'subscribe' - UNSUBSCRIBE = 'unsubscribe' - FLATTR = 'flattr' + SUBSCRIBE = "subscribe" + UNSUBSCRIBE = "unsubscribe" + FLATTR = "flattr" PODCAST_ACTIONS = ( - (SUBSCRIBE, 'subscribed'), - (UNSUBSCRIBE, 'unsubscribed'), - (FLATTR, 'flattr\'d'), + (SUBSCRIBE, "subscribed"), + (UNSUBSCRIBE, "unsubscribed"), + (FLATTR, "flattr'd"), ) # the timestamp at which the event happened @@ -46,9 +46,9 @@ class HistoryEntry(models.Model): ) class Meta: - index_together = [['user', 'client'], ['user', 'podcast']] + index_together = [["user", "client"], ["user", "podcast"]] - ordering = ['-timestamp'] + ordering = ["-timestamp"] verbose_name_plural = "History Entries" @@ -58,17 +58,17 @@ class Meta: class EpisodeHistoryEntry(models.Model): - DOWNLOAD = 'download' - PLAY = 'play' - DELETE = 'delete' - NEW = 'new' - FLATTR = 'flattr' + DOWNLOAD = "download" + PLAY = "play" + DELETE = "delete" + NEW = "new" + FLATTR = "flattr" EPISODE_ACTIONS = ( - (DOWNLOAD, 'downloaded'), - (PLAY, 'played'), - (DELETE, 'deleted'), - (NEW, 'marked as new'), - (FLATTR, 'flattr\'d'), + (DOWNLOAD, "downloaded"), + (PLAY, "played"), + (DELETE, "deleted"), + (NEW, "marked as new"), + (FLATTR, "flattr'd"), ) # the timestamp at which the event happened (provided by the client) @@ -111,21 +111,21 @@ class EpisodeHistoryEntry(models.Model): class Meta: index_together = [ - ['user', 'client', 'episode', 'action', 'timestamp'], + ["user", "client", "episode", "action", "timestamp"], # see query in played_episode_counts() - ['user', 'action', 'episode'], - ['user', 'episode', 'timestamp'], - ['user', 'timestamp'], - ['episode', 'timestamp'], + ["user", "action", "episode"], + ["user", "episode", "timestamp"], + ["user", "timestamp"], + ["episode", "timestamp"], ] - ordering = ['-timestamp'] + ordering = ["-timestamp"] verbose_name_plural = "Episode History Entries" def clean(self): """ Validates allowed combinations of time-values """ - PLAY_ACTION_KEYS = ('started', 'stopped', 'total') + PLAY_ACTION_KEYS = ("started", "stopped", "total") # Key found, but must not be supplied (no play action!) if self.action != EpisodeHistoryEntry.PLAY: @@ -137,13 +137,13 @@ def clean(self): if ( (self.started is not None) or (self.total is not None) ) and self.stopped is None: - raise ValidationError('started and total require position') + raise ValidationError("started and total require position") # Sanity check: total and playmark can only appear together if ((self.total is not None) or (self.started is not None)) and ( (self.total is None) or (self.started is None) ): - raise ValidationError('total and started can only appear together') + raise ValidationError("total and started can only appear together") @classmethod def create_entry( @@ -170,8 +170,8 @@ def create_entry( ).exists() if exists: logger.warning( - 'Trying to save duplicate {cls} for {user} ' - '/ {episode}'.format(cls=cls, user=user, episode=episode) + "Trying to save duplicate {cls} for {user} " + "/ {episode}".format(cls=cls, user=user, episode=episode) ) # if such an entry already exists, do nothing return @@ -208,7 +208,7 @@ def create_entry( except ValidationError as e: logger.warning( - 'Validation of {cls} failed for {user}: {err}'.format( + "Validation of {cls} failed for {user}: {err}".format( cls=cls, user=user, err=e ) ) diff --git a/mygpo/history/stats.py b/mygpo/history/stats.py index 849b14531..ff111d07a 100644 --- a/mygpo/history/stats.py +++ b/mygpo/history/stats.py @@ -13,9 +13,9 @@ def played_episode_counts(user): # about which episodes exactly have been played, only the number podcasts = ( EpisodeHistoryEntry.objects.filter(user=user, action=EpisodeHistoryEntry.PLAY) - .order_by('episode__id') - .distinct('episode__id') - .values_list('episode__podcast', flat=True) + .order_by("episode__id") + .distinct("episode__id") + .values_list("episode__podcast", flat=True) ) return Counter(podcasts) @@ -24,8 +24,8 @@ def num_played_episodes(user, since=None, until=None): """ Number of distinct episodes the user has played in the interval """ query = ( EpisodeHistoryEntry.objects.filter(user=user, action=EpisodeHistoryEntry.PLAY) - .order_by('episode__id') - .distinct('episode__id') + .order_by("episode__id") + .distinct("episode__id") ) if since is not None: @@ -41,15 +41,15 @@ def last_played_episodes(user, limit=10): """ The last episodes that the user played """ ep_ids = ( EpisodeHistoryEntry.objects.filter(user=user, action=EpisodeHistoryEntry.PLAY) - .order_by('episode__id', '-timestamp') - .distinct('episode__id') - .values_list('episode__id') + .order_by("episode__id", "-timestamp") + .distinct("episode__id") + .values_list("episode__id") ) ep_ids = ep_ids[:limit] episodes = ( Episode.objects.filter(id__in=ep_ids) - .select_related('podcast') - .prefetch_related('slugs', 'podcast__slugs') + .select_related("podcast") + .prefetch_related("slugs", "podcast__slugs") ) return episodes @@ -58,22 +58,22 @@ def seconds_played(user, since=None): """ The seconds played by the user since the given timestamp """ query = EpisodeHistoryEntry.objects.filter( user=user, action=EpisodeHistoryEntry.PLAY, stopped__isnull=False - ).extra(select={'seconds': 'stopped-COALESCE(started, 0)'}) + ).extra(select={"seconds": "stopped-COALESCE(started, 0)"}) if since is not None: query = query.filter(timestamp__gt=since) - seconds = query.values_list('seconds', flat=True) + seconds = query.values_list("seconds", flat=True) return sum([0] + list(seconds)) def playcounts_timerange(historyentries): """ returns {date: play-count} containing all days w/ play events""" listeners = ( - historyentries.extra({'date': "date_trunc('day', timestamp)"}) - .values('date') - .order_by('date') - .annotate(count=Count('pk')) + historyentries.extra({"date": "date_trunc('day', timestamp)"}) + .values("date") + .order_by("date") + .annotate(count=Count("pk")) ) - return {x['date'].date(): x['count'] for x in listeners} + return {x["date"].date(): x["count"] for x in listeners} diff --git a/mygpo/history/urls.py b/mygpo/history/urls.py index 778518a8c..9f42e49cc 100644 --- a/mygpo/history/urls.py +++ b/mygpo/history/urls.py @@ -4,20 +4,20 @@ from mygpo.users import converters -register_converter(converters.ClientUIDConverter, 'client-uid') +register_converter(converters.ClientUIDConverter, "client-uid") urlpatterns = [ - path('history/', views.history, name='history'), + path("history/", views.history, name="history"), path( - 'podcast//+history', + "podcast//+history", views.history_podcast_id, - name='podcast-history-id', + name="podcast-history-id", ), path( - 'podcast//+history', + "podcast//+history", views.history_podcast_slug, - name='podcast-history-slug', + name="podcast-history-slug", ), - path('device//history', views.history, name='device-history'), + path("device//history", views.history, name="device-history"), ] diff --git a/mygpo/history/views.py b/mygpo/history/views.py index ab5d9df54..9cde1fa3e 100644 --- a/mygpo/history/views.py +++ b/mygpo/history/views.py @@ -16,7 +16,7 @@ def history(request, count=15, uid=None): user = request.user client = None - history = HistoryEntry.objects.filter(user=user).select_related('podcast') + history = HistoryEntry.objects.filter(user=user).select_related("podcast") if uid: try: @@ -30,7 +30,7 @@ def history(request, count=15, uid=None): paginator = Paginator(history, count) - page = request.GET.get('page') + page = request.GET.get("page") try: entries = paginator.page(page) @@ -42,7 +42,7 @@ def history(request, count=15, uid=None): entries = paginator.page(paginator.num_pages) return render( - request, 'history.html', {'history': entries, 'client': client, 'page': page} + request, "history.html", {"history": entries, "client": client, "page": page} ) @@ -55,7 +55,7 @@ def podcast_history(request, podcast): history = HistoryEntry.objects.filter(user=request.user, podcast=podcast) return render( - request, 'podcast-history.html', {'history': history, 'podcast': podcast} + request, "podcast-history.html", {"history": history, "podcast": podcast} ) diff --git a/mygpo/maintenance/management/podcastcmd.py b/mygpo/maintenance/management/podcastcmd.py index 423fcf838..14d00e41a 100644 --- a/mygpo/maintenance/management/podcastcmd.py +++ b/mygpo/maintenance/management/podcastcmd.py @@ -12,86 +12,86 @@ class PodcastCommand(BaseCommand): def add_arguments(self, parser): parser.add_argument( - '--toplist', - action='store_true', - dest='toplist', + "--toplist", + action="store_true", + dest="toplist", default=False, help="Update all entries from the Toplist.", ), parser.add_argument( - '--update-new', - action='store_true', - dest='new', + "--update-new", + action="store_true", + dest="new", default=False, help="Update all podcasts with new Episodes", ), parser.add_argument( - '--max', - action='store', - dest='max', + "--max", + action="store", + dest="max", type=int, default=0, help="Set how many feeds should be updated at maximum", ), parser.add_argument( - '--random', - action='store_true', - dest='random', + "--random", + action="store_true", + dest="random", default=False, help="Update random podcasts, best used with --max", ), parser.add_argument( - '--next', - action='store_true', - dest='next', + "--next", + action="store_true", + dest="next", default=False, help="Podcasts that are due to be updated next", ), - parser.add_argument('urls', nargs='+', type=str) + parser.add_argument("urls", nargs="+", type=str) def get_podcasts(self, *args, **options): return chain.from_iterable(self._get_podcasts(*args, **options)) def _get_podcasts(self, *args, **options): - max_podcasts = options.get('max') + max_podcasts = options.get("max") - if options.get('toplist'): + if options.get("toplist"): yield (p.url for p in self.get_toplist(max_podcasts)) - if options.get('new'): + if options.get("new"): query = Podcast.objects.filter( episode__title__isnull=True, episode__outdated=False ) - podcasts = query.distinct('id')[:max_podcasts] + podcasts = query.distinct("id")[:max_podcasts] random.shuffle(podcasts) yield (p.url for p in podcasts) - if options.get('random'): + if options.get("random"): podcasts = random_podcasts() yield (p.url for p in podcasts) - if options.get('next'): + if options.get("next"): podcasts = Podcast.objects.all().order_by_next_update()[:max_podcasts] yield (p.url for p in podcasts) - if options.get('urls'): - yield options.get('urls') + if options.get("urls"): + yield options.get("urls") if ( - not options.get('urls') - and not options.get('toplist') - and not options.get('new') - and not options.get('random') - and not options.get('next') + not options.get("urls") + and not options.get("toplist") + and not options.get("new") + and not options.get("random") + and not options.get("next") ): - query = Podcast.objects.order_by('last_update') - podcasts = query.select_related('urls')[:max_podcasts] + query = Podcast.objects.order_by("last_update") + podcasts = query.select_related("urls")[:max_podcasts] yield (p.url for p in podcasts) def get_toplist(self, max_podcasts=100): diff --git a/mygpo/maintenance/merge.py b/mygpo/maintenance/merge.py index 1f1e9d34e..129285414 100644 --- a/mygpo/maintenance/merge.py +++ b/mygpo/maintenance/merge.py @@ -55,10 +55,10 @@ def __init__(self, podcasts, actions, groups): def merge(self): """ Carries out the actual merging """ - logger.info('Start merging of podcasts: %r', self.podcasts) + logger.info("Start merging of podcasts: %r", self.podcasts) podcast1 = self.podcasts.pop(0) - logger.info('Merge target: %r', podcast1) + logger.info("Merge target: %r", podcast1) self.merge_episodes() merge_model_objects(podcast1, self.podcasts) @@ -74,7 +74,7 @@ def merge_episodes(self): episode_id = episodes.pop(0) episode = Episode.objects.get(pk=episode_id) - logger.info('Merging %d episodes', len(episodes)) + logger.info("Merging %d episodes", len(episodes)) eps = [Episode.objects.get(pk=eid) for eid in episodes] merge_model_objects(episode, eps) @@ -91,7 +91,7 @@ def reassign_urls(obj1, obj2): try: url.save() except IntegrityError as ie: - logger.warning('Moving URL failed: %s. Deleting.', str(ie)) + logger.warning("Moving URL failed: %s. Deleting.", str(ie)) url.delete() @@ -113,7 +113,7 @@ def reassign_slugs(obj1, obj2): try: slug.save() except IntegrityError as ie: - logger.warning('Moving Slug failed: %s. Deleting', str(ie)) + logger.warning("Moving Slug failed: %s. Deleting", str(ie)) slug.delete() @@ -139,11 +139,11 @@ def merge_model_objects(primary_object, alias_objects=[], keep_old=False): primary_class = primary_object.__class__ if not issubclass(primary_class, Model): - raise TypeError('Only django.db.models.Model subclasses can be merged') + raise TypeError("Only django.db.models.Model subclasses can be merged") for alias_object in alias_objects: if not isinstance(alias_object, primary_class): - raise TypeError('Only models of same class can be merged') + raise TypeError("Only models of same class can be merged") # Get a list of all GenericForeignKeys in all models # TODO: this is a bit of a hack, since the generics framework should @@ -161,7 +161,7 @@ def merge_model_objects(primary_object, alias_objects=[], keep_old=False): [ field.attname for field in primary_object._meta.local_fields - if getattr(primary_object, field.attname) in [None, ''] + if getattr(primary_object, field.attname) in [None, ""] ] ) @@ -223,7 +223,7 @@ def merge_model_objects(primary_object, alias_objects=[], keep_old=False): filled_up = set() for field_name in blank_local_fields: val = getattr(alias_object, field_name) - if val not in [None, '']: + if val not in [None, ""]: setattr(primary_object, field_name, val) filled_up.add(field_name) blank_local_fields -= filled_up @@ -277,7 +277,7 @@ def reassigned(obj, new): else: raise TypeError( - 'unknown type for reassigning: {objtype}'.format(objtype=type(obj)) + "unknown type for reassigning: {objtype}".format(objtype=type(obj)) ) @@ -299,7 +299,7 @@ def before_delete(old, new): else: raise TypeError( - 'unknown type for deleting: {objtype}'.format(objtype=type(old)) + "unknown type for deleting: {objtype}".format(objtype=type(old)) ) @@ -311,4 +311,4 @@ def merge(moved_obj, new_target): pass else: - raise TypeError('unknown type for merging: {objtype}'.format(objtype=type(old))) + raise TypeError("unknown type for merging: {objtype}".format(objtype=type(old))) diff --git a/mygpo/maintenance/tests.py b/mygpo/maintenance/tests.py index cd0d44ec0..9ee69ad06 100644 --- a/mygpo/maintenance/tests.py +++ b/mygpo/maintenance/tests.py @@ -19,22 +19,22 @@ def u(): class SimpleMergeTests(TestCase): def setUp(self): self.podcast1 = Podcast.objects.get_or_create_for_url( - 'http://example.com/simple-merge-test-feed.rss', - defaults={'title': 'Podcast 1'}, + "http://example.com/simple-merge-test-feed.rss", + defaults={"title": "Podcast 1"}, ).object self.podcast2 = Podcast.objects.get_or_create_for_url( - 'http://simple-merge-test.org/podcast/', defaults={'title': 'Podcast 2'} + "http://simple-merge-test.org/podcast/", defaults={"title": "Podcast 2"} ).object self.episode1 = Episode.objects.get_or_create_for_url( self.podcast1, - 'http://example.com/simple-merge-test-episode1.mp3', - defaults={'title': 'Episode 1 A'}, + "http://example.com/simple-merge-test-episode1.mp3", + defaults={"title": "Episode 1 A"}, ).object self.episode2 = Episode.objects.get_or_create_for_url( self.podcast2, - 'http://example.com/simple-merge-test-episode1.mp3', - defaults={'title': 'Episode 1 B'}, + "http://example.com/simple-merge-test-episode1.mp3", + defaults={"title": "Episode 1 B"}, ).object def test_merge_podcasts(self): @@ -51,27 +51,27 @@ class MergeTests(TransactionTestCase): def setUp(self): self.podcast1 = Podcast.objects.get_or_create_for_url( - 'http://example.com/merge-test-feed.rss', defaults={'title': 'Podcast 1'} + "http://example.com/merge-test-feed.rss", defaults={"title": "Podcast 1"} ).object self.podcast2 = Podcast.objects.get_or_create_for_url( - 'http://merge-test.org/podcast/', defaults={'title': 'Podcast 2'} + "http://merge-test.org/podcast/", defaults={"title": "Podcast 2"} ).object self.episode1 = Episode.objects.get_or_create_for_url( self.podcast1, - 'http://example.com/merge-test-episode1.mp3', - defaults={'title': 'Episode 1 A'}, + "http://example.com/merge-test-episode1.mp3", + defaults={"title": "Episode 1 A"}, ).object self.episode2 = Episode.objects.get_or_create_for_url( self.podcast2, - 'http://example.com/merge-test-episode1.mp3', - defaults={'title': 'Episode 1 B'}, + "http://example.com/merge-test-episode1.mp3", + defaults={"title": "Episode 1 B"}, ).object User = get_user_model() - self.user = User(username='test-merge') - self.user.email = 'test-merge-tests@example.com' - self.user.set_password('secret!') + self.user = User(username="test-merge") + self.user.email = "test-merge-tests@example.com" + self.user.set_password("secret!") self.user.save() def test_merge_podcasts(self): @@ -110,37 +110,37 @@ class MergeGroupTests(TransactionTestCase): def setUp(self): self.podcast1 = Podcast.objects.get_or_create_for_url( - 'http://example.com/group-merge-feed.rss', defaults={'title': 'Podcast 1'} + "http://example.com/group-merge-feed.rss", defaults={"title": "Podcast 1"} ).object self.podcast2 = Podcast.objects.get_or_create_for_url( - 'http://test.org/group-merge-podcast/', defaults={'title': 'Podcast 2'} + "http://test.org/group-merge-podcast/", defaults={"title": "Podcast 2"} ).object self.podcast3 = Podcast.objects.get_or_create_for_url( - 'http://group-test.org/feed/', defaults={'title': 'Podcast 3'} + "http://group-test.org/feed/", defaults={"title": "Podcast 3"} ).object self.episode1 = Episode.objects.get_or_create_for_url( self.podcast1, - 'http://example.com/group-merge-episode1.mp3', - defaults={'title': 'Episode 1 A'}, + "http://example.com/group-merge-episode1.mp3", + defaults={"title": "Episode 1 A"}, ).object self.episode2 = Episode.objects.get_or_create_for_url( self.podcast2, - 'http://example.com/group-merge-episode1.mp3', - defaults={'title': 'Episode 1 B'}, + "http://example.com/group-merge-episode1.mp3", + defaults={"title": "Episode 1 B"}, ).object self.episode3 = Episode.objects.get_or_create_for_url( self.podcast3, - 'http://example.com/group-merge-media.mp3', - defaults={'title': 'Episode 2'}, + "http://example.com/group-merge-media.mp3", + defaults={"title": "Episode 2"}, ).object - self.podcast2.group_with(self.podcast3, 'My Group', 'Feed1', 'Feed2') + self.podcast2.group_with(self.podcast3, "My Group", "Feed1", "Feed2") User = get_user_model() - self.user = User(username='test-merge-group') - self.user.email = 'test-merge-group-tests@example.com' - self.user.set_password('secret!') + self.user = User(username="test-merge-group") + self.user.email = "test-merge-group-tests@example.com" + self.user.set_password("secret!") self.user.save() def test_merge_podcasts(self): diff --git a/mygpo/podcastlists/admin.py b/mygpo/podcastlists/admin.py index 0863bcc46..04d5d429c 100644 --- a/mygpo/podcastlists/admin.py +++ b/mygpo/podcastlists/admin.py @@ -13,15 +13,15 @@ class PodcastListAdmin(admin.ModelAdmin): """ Admin page for podcast lists""" # configuration for the list view - list_display = ('title', 'user', 'slug', 'num_entries', 'vote_count') + list_display = ("title", "user", "slug", "num_entries", "vote_count") # fetch related objects for the list view - list_select_related = ('user',) + list_select_related = ("user",) - search_fields = ('title', 'user__username', 'slug') + search_fields = ("title", "user__username", "slug") inlines = [PodcastListEntryInline, VoteInline] - raw_id_fields = ('user',) + raw_id_fields = ("user",) show_full_result_count = False diff --git a/mygpo/podcastlists/migrations/0001_initial.py b/mygpo/podcastlists/migrations/0001_initial.py index c7d30fd46..6abe8cd6f 100644 --- a/mygpo/podcastlists/migrations/0001_initial.py +++ b/mygpo/podcastlists/migrations/0001_initial.py @@ -10,23 +10,23 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0001_initial'), + ("contenttypes", "0001_initial"), ] operations = [ migrations.CreateModel( - name='PodcastList', + name="PodcastList", fields=[ ( - 'id', + "id", models.UUIDField(max_length=32, serialize=False, primary_key=True), ), - ('title', models.CharField(max_length=512)), - ('slug', models.SlugField(max_length=128)), - ('created', models.DateTimeField()), - ('modified', models.DateTimeField()), + ("title", models.CharField(max_length=512)), + ("slug", models.SlugField(max_length=128)), + ("created", models.DateTimeField()), + ("modified", models.DateTimeField()), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -36,33 +36,33 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.CreateModel( - name='PodcastListEntry', + name="PodcastListEntry", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('order', models.PositiveSmallIntegerField()), - ('object_id', models.UUIDField(max_length=32)), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), + ("order", models.PositiveSmallIntegerField()), + ("object_id", models.UUIDField(max_length=32)), ( - 'content_type', + "content_type", models.ForeignKey( - to='contenttypes.ContentType', + to="contenttypes.ContentType", on_delete=django.db.models.deletion.CASCADE, ), ), ( - 'podcastlist', + "podcastlist", models.ForeignKey( - related_name='entries', - to='podcastlists.PodcastList', + related_name="entries", + to="podcastlists.PodcastList", on_delete=models.CASCADE, ), ), @@ -71,12 +71,12 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='podcastlistentry', + name="podcastlistentry", unique_together=set( - [('podcastlist', 'order'), ('podcastlist', 'content_type', 'object_id')] + [("podcastlist", "order"), ("podcastlist", "content_type", "object_id")] ), ), migrations.AlterUniqueTogether( - name='podcastlist', unique_together=set([('user', 'slug')]) + name="podcastlist", unique_together=set([("user", "slug")]) ), ] diff --git a/mygpo/podcastlists/migrations/0002_updateinfomodel.py b/mygpo/podcastlists/migrations/0002_updateinfomodel.py index 847b014a3..2f436663a 100644 --- a/mygpo/podcastlists/migrations/0002_updateinfomodel.py +++ b/mygpo/podcastlists/migrations/0002_updateinfomodel.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcastlists', '0001_initial')] + dependencies = [("podcastlists", "0001_initial")] operations = [ migrations.AlterField( - model_name='podcastlist', - name='created', + model_name="podcastlist", + name="created", field=models.DateTimeField(auto_now_add=True), ), migrations.AlterField( - model_name='podcastlist', - name='modified', + model_name="podcastlist", + name="modified", field=models.DateTimeField(auto_now=True), ), ] diff --git a/mygpo/podcastlists/migrations/0003_entries_ordering.py b/mygpo/podcastlists/migrations/0003_entries_ordering.py index 59e593c2c..76ac3e00c 100644 --- a/mygpo/podcastlists/migrations/0003_entries_ordering.py +++ b/mygpo/podcastlists/migrations/0003_entries_ordering.py @@ -6,10 +6,10 @@ class Migration(migrations.Migration): - dependencies = [('podcastlists', '0002_updateinfomodel')] + dependencies = [("podcastlists", "0002_updateinfomodel")] operations = [ migrations.AlterModelOptions( - name='podcastlistentry', options={'ordering': ['order']} + name="podcastlistentry", options={"ordering": ["order"]} ) ] diff --git a/mygpo/podcastlists/migrations/0004_django_uuidfield.py b/mygpo/podcastlists/migrations/0004_django_uuidfield.py index 6ac12e3fb..3783d9d59 100644 --- a/mygpo/podcastlists/migrations/0004_django_uuidfield.py +++ b/mygpo/podcastlists/migrations/0004_django_uuidfield.py @@ -4,15 +4,15 @@ class Migration(migrations.Migration): - dependencies = [('podcastlists', '0003_entries_ordering')] + dependencies = [("podcastlists", "0003_entries_ordering")] operations = [ migrations.AlterField( - model_name='podcastlist', - name='id', + model_name="podcastlist", + name="id", field=models.UUIDField(serialize=False, primary_key=True), ), migrations.AlterField( - model_name='podcastlistentry', name='object_id', field=models.UUIDField() + model_name="podcastlistentry", name="object_id", field=models.UUIDField() ), ] diff --git a/mygpo/podcastlists/models.py b/mygpo/podcastlists/models.py index c90903341..42b1258d9 100644 --- a/mygpo/podcastlists/models.py +++ b/mygpo/podcastlists/models.py @@ -25,7 +25,7 @@ class PodcastList(UUIDModel, VoteMixin, UpdateInfoModel): class Meta: unique_together = [ # a slug is unique for a user - ('user', 'slug') + ("user", "slug") ] @property @@ -41,7 +41,7 @@ def add_entry(self, obj): podcastlist=self, content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, - defaults={'order': self.max_order + 1}, + defaults={"order": self.max_order + 1}, ) def set_entries(self, podcasts): @@ -49,7 +49,7 @@ def set_entries(self, podcasts): existing = {e.content_object: e for e in self.entries.all()} set_ordered_entries( - self, podcasts, existing, PodcastListEntry, 'content_object', 'podcastlist' + self, podcasts, existing, PodcastListEntry, "content_object", "podcastlist" ) @@ -58,16 +58,16 @@ class PodcastListEntry(UpdateInfoModel, OrderedModel): # the list that the entry belongs to podcastlist = models.ForeignKey( - PodcastList, related_name='entries', on_delete=models.CASCADE + PodcastList, related_name="entries", on_delete=models.CASCADE ) # the object (Podcast or PodcastGroup) that is in the list content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") class Meta(OrderedModel.Meta): unique_together = [ - ('podcastlist', 'order'), - ('podcastlist', 'content_type', 'object_id'), + ("podcastlist", "order"), + ("podcastlist", "content_type", "object_id"), ] diff --git a/mygpo/podcastlists/tests.py b/mygpo/podcastlists/tests.py index c69e090b5..e6fe403c8 100644 --- a/mygpo/podcastlists/tests.py +++ b/mygpo/podcastlists/tests.py @@ -13,7 +13,7 @@ class TestAPI(TestCase): def setUp(self): self.user, pwd = create_user() self.client = Client() - self.extra = {'HTTP_AUTHORIZATION': create_auth_string(self.user.username, pwd)} + self.extra = {"HTTP_AUTHORIZATION": create_auth_string(self.user.username, pwd)} def tearDown(self): self.user.delete() @@ -21,25 +21,25 @@ def tearDown(self): def test_create_missing_title(self): """ verify error response when creating podcast list w/o title """ url = reverse( - views.create, kwargs={'username': self.user.username, 'format': 'txt'} + views.create, kwargs={"username": self.user.username, "format": "txt"} ) - urls = ['http://example.com/podcast.rss', 'http://example.com/asdf.xml'] + urls = ["http://example.com/podcast.rss", "http://example.com/asdf.xml"] resp = self.client.post( - url, '\n'.join(urls), content_type="text/plain", **self.extra + url, "\n".join(urls), content_type="text/plain", **self.extra ) self.assertEqual(resp.status_code, 400, resp.content) def test_create(self): """ Create a podcast list and verify """ - title = 'My Podcast List' - url = get_create_url(self.user.username, 'txt', title) + title = "My Podcast List" + url = get_create_url(self.user.username, "txt", title) - urls = ['http://example.com/podcast.rss', 'http://example.com/asdf.xml'] + urls = ["http://example.com/podcast.rss", "http://example.com/asdf.xml"] resp = self.client.post( - url, '\n'.join(urls), content_type="text/plain", **self.extra + url, "\n".join(urls), content_type="text/plain", **self.extra ) self.assertEqual(resp.status_code, 201, resp.content) @@ -53,17 +53,17 @@ def test_create(self): def test_replace(self): """ Create, replace and delete a podcast list """ - title = 'My Podcast List' - url = get_create_url(self.user.username, 'txt', title) + title = "My Podcast List" + url = get_create_url(self.user.username, "txt", title) urls1 = [ - 'http://example.com/podcast.rss', - 'http://example.com/asdf.xml', - 'http://example.com/test.rss', + "http://example.com/podcast.rss", + "http://example.com/asdf.xml", + "http://example.com/test.rss", ] resp = self.client.post( - url, '\n'.join(urls1), content_type="text/plain", **self.extra + url, "\n".join(urls1), content_type="text/plain", **self.extra ) self.assertEqual(resp.status_code, 201, resp.content) @@ -74,21 +74,21 @@ def test_replace(self): # replace existing list; the lists's URL is returned # in the Location header - url = resp['Location'] + url = resp["Location"] urls2 = [ - 'http://example.com/test.rss', # reordered - 'http://example.com/asdf.xml', - 'http://example.com/new.rss', + "http://example.com/test.rss", # reordered + "http://example.com/asdf.xml", + "http://example.com/new.rss", ] # new resp = self.client.put( - url, '\n'.join(urls2), content_type="text/plain", **self.extra + url, "\n".join(urls2), content_type="text/plain", **self.extra ) self.assertEqual(resp.status_code, 204, resp.content) # assert that the list has actually been updated resp = self.client.get(url, content_type="text/plain", **self.extra) - resp_urls = [u for u in resp.content.decode('utf-8').split('\n') if u] + resp_urls = [u for u in resp.content.decode("utf-8").split("\n") if u] self.assertEqual(urls2, resp_urls) # delete the list @@ -96,7 +96,7 @@ def test_replace(self): def get_create_url(username, fmt, title): - return '{url}?title={title}'.format( - url=reverse(views.create, kwargs={'username': username, 'format': fmt}), + return "{url}?title={title}".format( + url=reverse(views.create, kwargs={"username": username, "format": fmt}), title=title, ) diff --git a/mygpo/podcastlists/urls.py b/mygpo/podcastlists/urls.py index 3ee1e4e6c..ae16179ed 100644 --- a/mygpo/podcastlists/urls.py +++ b/mygpo/podcastlists/urls.py @@ -5,31 +5,31 @@ from mygpo.users import converters -register_converter(converters.UsernameConverter, 'username') +register_converter(converters.UsernameConverter, "username") userpatterns = [ - path('lists/', views.lists_user, name='lists-user'), - path('list/', views.list_show, name='list-show'), - path('list/.opml', views.list_opml, name='list-opml'), - path('list//search', views.search, name='list-search'), + path("lists/", views.lists_user, name="lists-user"), + path("list/", views.list_show, name="list-show"), + path("list/.opml", views.list_opml, name="list-opml"), + path("list//search", views.search, name="list-search"), path( - 'list//add/', + "list//add/", views.add_podcast, - name='list-add-podcast', + name="list-add-podcast", ), path( - 'list//remove/', + "list//remove/", views.remove_podcast, - name='list-remove-podcast', + name="list-remove-podcast", ), - path('list//delete', views.delete_list, name='list-delete'), - path('list//rate', views.rate_list, name='list-rate'), + path("list//delete", views.delete_list, name="list-delete"), + path("list//rate", views.rate_list, name="list-rate"), ] urlpatterns = [ - path('share/lists/', views.lists_own, name='lists-overview'), - path('share/lists/create', views.create_list, name='list-create'), - path('user//', include(userpatterns)), + path("share/lists/", views.lists_own, name="lists-overview"), + path("share/lists/create", views.create_list, name="list-create"), + path("user//", include(userpatterns)), ] diff --git a/mygpo/podcastlists/views.py b/mygpo/podcastlists/views.py index 79dd6989c..06c02de98 100644 --- a/mygpo/podcastlists/views.py +++ b/mygpo/podcastlists/views.py @@ -47,7 +47,7 @@ def _decorator(request, username, slug, *args, **kwargs): @list_decorator(must_own=False) def search(request, plist, owner): - return directory_search(request, 'list_search.html', {'listname': plist.slug}) + return directory_search(request, "list_search.html", {"listname": plist.slug}) @login_required @@ -55,7 +55,7 @@ def lists_own(request): lists = PodcastList.objects.filter(user=request.user) - return render(request, 'lists.html', {'lists': lists}) + return render(request, "lists.html", {"lists": lists}) def lists_user(request, username): @@ -64,7 +64,7 @@ def lists_user(request, username): user = get_object_or_404(User, username=username) lists = PodcastList.objects.filter(user=user) - return render(request, 'lists_user.html', {'lists': lists, 'user': user}) + return render(request, "lists_user.html", {"lists": lists, "user": user}) @list_decorator(must_own=False) @@ -78,13 +78,13 @@ def list_show(request, plist, owner): return render( request, - 'list.html', + "list.html", { - 'podcastlist': plist, - 'max_subscribers': max_subscribers, - 'owner': owner, - 'domain': site.domain, - 'is_own': is_own, + "podcastlist": plist, + "max_subscribers": max_subscribers, + "owner": owner, + "domain": site.domain, + "is_own": is_own, }, ) @@ -92,28 +92,28 @@ def list_show(request, plist, owner): @list_decorator(must_own=False) def list_opml(request, plist, owner): podcasts = [entry.content_object for entry in plist.entries.all()] - return format_podcast_list(podcasts, 'opml', plist.title) + return format_podcast_list(podcasts, "opml", plist.title) @login_required def create_list(request): - title = request.POST.get('title', None) + title = request.POST.get("title", None) if not title: - messages.error(request, _('You have to specify a title.')) - return HttpResponseRedirect(reverse('lists-overview')) + messages.error(request, _("You have to specify a title.")) + return HttpResponseRedirect(reverse("lists-overview")) slug = slugify(title) if not slug: messages.error(request, _('"{title}" is not a valid title').format(title=title)) - return HttpResponseRedirect(reverse('lists-overview')) + return HttpResponseRedirect(reverse("lists-overview")) plist, created = PodcastList.objects.get_or_create( - user=request.user, slug=slug, defaults={'id': uuid.uuid1(), 'title': title} + user=request.user, slug=slug, defaults={"id": uuid.uuid1(), "title": title} ) - list_url = reverse('list-show', args=[request.user.username, slug]) + list_url = reverse("list-show", args=[request.user.username, slug]) return HttpResponseRedirect(list_url) @@ -130,7 +130,7 @@ def add_podcast(request, plist, owner, podcast_id): raise Http404 plist.add_entry(obj) - list_url = reverse('list-show', args=[owner.username, plist.slug]) + list_url = reverse("list-show", args=[owner.username, plist.slug]) return HttpResponseRedirect(list_url) @@ -138,7 +138,7 @@ def add_podcast(request, plist, owner, podcast_id): @list_decorator(must_own=True) def remove_podcast(request, plist, owner, order): PodcastListEntry.objects.filter(podcastlist=plist, order=order).delete() - list_url = reverse('list-show', args=[owner.username, plist.slug]) + list_url = reverse("list-show", args=[owner.username, plist.slug]) return HttpResponseRedirect(list_url) @@ -146,7 +146,7 @@ def remove_podcast(request, plist, owner, order): @list_decorator(must_own=True) def delete_list(request, plist, owner): plist.delete() - return HttpResponseRedirect(reverse('lists-overview')) + return HttpResponseRedirect(reverse("lists-overview")) @login_required @@ -159,7 +159,7 @@ def rate_list(request, plist, owner): content_type=ContentType.objects.get_for_model(plist), object_id=plist.id, ) - messages.success(request, _('Thanks for rating!')) + messages.success(request, _("Thanks for rating!")) - list_url = reverse('list-show', args=[owner.username, plist.slug]) + list_url = reverse("list-show", args=[owner.username, plist.slug]) return HttpResponseRedirect(list_url) diff --git a/mygpo/podcasts/admin.py b/mygpo/podcasts/admin.py index e0a180008..5ed6cc831 100644 --- a/mygpo/podcasts/admin.py +++ b/mygpo/podcasts/admin.py @@ -21,12 +21,12 @@ def admin_link(self, instance): """ Link to the admin page """ if not instance.pk: - return '' + return "" url = edit_link(instance) - return format_html('{}', url, _('Edit')) + return format_html('{}', url, _("Edit")) - readonly_fields = ('admin_link',) + readonly_fields = ("admin_link",) class AdminLinkInline(AdminLinkMixin, admin.TabularInline): @@ -40,15 +40,15 @@ class GenericAdminLinkInline(AdminLinkMixin, GenericTabularInline): @admin.register(URL) class URLAdmin(admin.ModelAdmin): model = URL - list_display = ('url', 'content_type', 'object_id') - list_filter = ('content_type',) + list_display = ("url", "content_type", "object_id") + list_filter = ("content_type",) show_full_result_count = False class URLInline(GenericAdminLinkInline): model = URL - fields = ('order', 'url', 'admin_link') + fields = ("order", "url", "admin_link") class SlugInline(GenericTabularInline): @@ -58,7 +58,7 @@ class SlugInline(GenericTabularInline): class TagInline(GenericTabularInline): model = Tag - raw_id_fields = ('user',) + raw_id_fields = ("user",) class MergedUUIDInline(GenericTabularInline): @@ -68,9 +68,9 @@ class MergedUUIDInline(GenericTabularInline): class PodcastInline(AdminLinkInline): model = Podcast - fields = ('id', 'title', 'group_member_name', 'admin_link') + fields = ("id", "title", "group_member_name", "admin_link") - readonly_fields = ('id',) + AdminLinkInline.readonly_fields + readonly_fields = ("id",) + AdminLinkInline.readonly_fields can_delete = False @@ -84,68 +84,68 @@ class PodcastAdmin(admin.ModelAdmin): """ Admin page for podcasts """ # configuration for the list view - list_display = ('title', 'main_url') + list_display = ("title", "main_url") - list_filter = ('language',) - search_fields = ('title', 'twitter', '^urls__url') + list_filter = ("language",) + search_fields = ("title", "twitter", "^urls__url") # configuration for the create / edit view fieldsets = ( ( None, - {'fields': ('id', 'title', 'subtitle', 'description', 'link', 'language')}, + {"fields": ("id", "title", "subtitle", "description", "link", "language")}, ), ( - 'Additional information', + "Additional information", { - 'fields': ( - 'created', - 'license', - 'flattr_url', - 'author', - 'twitter', - 'related_podcasts', + "fields": ( + "created", + "license", + "flattr_url", + "author", + "twitter", + "related_podcasts", ) }, ), - ('Podcast Group', {'fields': ('group', 'group_member_name')}), + ("Podcast Group", {"fields": ("group", "group_member_name")}), ( - 'Episodes', + "Episodes", { - 'fields': ( - 'common_episode_title', - 'latest_episode_timestamp', - 'content_types', - 'max_episode_order', - 'episode_count', + "fields": ( + "common_episode_title", + "latest_episode_timestamp", + "content_types", + "max_episode_order", + "episode_count", ) }, ), ( - 'Feed updates', + "Feed updates", { - 'fields': ( - 'outdated', - 'new_location', - 'last_update', - 'search_index_uptodate', - 'search_vector', + "fields": ( + "outdated", + "new_location", + "last_update", + "search_index_uptodate", + "search_vector", ) }, ), - ('Admin', {'fields': ('restrictions', 'hub')}), + ("Admin", {"fields": ("restrictions", "hub")}), ) inlines = [URLInline, SlugInline, TagInline, MergedUUIDInline] - raw_id_fields = ('related_podcasts',) + raw_id_fields = ("related_podcasts",) readonly_fields = ( - 'id', - 'created', - 'last_update', - 'search_index_uptodate', - 'search_vector', + "id", + "created", + "last_update", + "search_index_uptodate", + "search_vector", ) show_full_result_count = False @@ -153,7 +153,7 @@ class PodcastAdmin(admin.ModelAdmin): def main_url(self, podcast): url = podcast.urls.first() if url is None: - return '' + return "" return url.url @@ -163,55 +163,55 @@ class EpisodeAdmin(admin.ModelAdmin): """ Admin page for episodes """ # configuration for the list view - list_display = ('title', 'podcast_title', 'main_url') + list_display = ("title", "podcast_title", "main_url") - list_filter = ('language',) - search_fields = ('title', '^urls__url') + list_filter = ("language",) + search_fields = ("title", "^urls__url") # configuration for the create / edit view fieldsets = ( ( None, { - 'fields': ( - 'id', - 'title', - 'subtitle', - 'description', - 'link', - 'language', - 'guid', - 'released', - 'podcast', - 'order', + "fields": ( + "id", + "title", + "subtitle", + "description", + "link", + "language", + "guid", + "released", + "podcast", + "order", ) }, ), ( - 'Additional information', + "Additional information", { - 'fields': ( - 'created', - 'license', - 'flattr_url', - 'author', - 'content', - 'listeners', + "fields": ( + "created", + "license", + "flattr_url", + "author", + "content", + "listeners", ) }, ), ( - 'Media file', - {'fields': ('duration', 'filesize', 'content_types', 'mimetypes')}, + "Media file", + {"fields": ("duration", "filesize", "content_types", "mimetypes")}, ), - ('Feed updates', {'fields': ('outdated', 'last_update')}), + ("Feed updates", {"fields": ("outdated", "last_update")}), ) inlines = [URLInline, SlugInline, MergedUUIDInline] - raw_id_fields = ('podcast',) + raw_id_fields = ("podcast",) - readonly_fields = ('id', 'created', 'last_update') + readonly_fields = ("id", "created", "last_update") show_full_result_count = False @@ -227,9 +227,9 @@ class PodcastGroupAdmin(admin.ModelAdmin): """ Admin page for podcast groups """ # configuration for the list view - list_display = ('title',) + list_display = ("title",) - search_fields = ('title',) + search_fields = ("title",) inlines = [PodcastInline] @@ -240,8 +240,8 @@ class PodcastGroupAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin): """ Admin page for tags """ - list_display = ('tag', 'content_object', 'source', 'user') + list_display = ("tag", "content_object", "source", "user") - list_filter = ('source',) + list_filter = ("source",) show_full_result_count = False diff --git a/mygpo/podcasts/migrations/0001_initial.py b/mygpo/podcasts/migrations/0001_initial.py index f4c9e6381..c28ba78f8 100644 --- a/mygpo/podcasts/migrations/0001_initial.py +++ b/mygpo/podcasts/migrations/0001_initial.py @@ -6,222 +6,222 @@ class Migration(migrations.Migration): - dependencies = [('contenttypes', '__first__')] + dependencies = [("contenttypes", "__first__")] operations = [ migrations.CreateModel( - name='PodcastGroup', + name="PodcastGroup", fields=[ ( - 'id', + "id", models.UUIDField(max_length=32, serialize=False, primary_key=True), ), - ('title', models.CharField(max_length=1000, blank=True)), - ('subtitle', models.CharField(max_length=1000, blank=True)), + ("title", models.CharField(max_length=1000, blank=True)), + ("subtitle", models.CharField(max_length=1000, blank=True)), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='MergedUUID', + name="MergedUUID", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('uuid', models.UUIDField(unique=True, max_length=32)), + ("uuid", models.UUIDField(unique=True, max_length=32)), ( - 'content_type', + "content_type", models.ForeignKey( - to='contenttypes.ContentType', - to_field='id', + to="contenttypes.ContentType", + to_field="id", on_delete=models.PROTECT, ), ), - ('object_id', models.UUIDField(max_length=32)), + ("object_id", models.UUIDField(max_length=32)), ], options={}, bases=(models.Model,), ), migrations.CreateModel( - name='Slug', + name="Slug", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('order', models.PositiveSmallIntegerField()), - ('scope', models.UUIDField(max_length=32, null=True)), - ('slug', models.SlugField()), + ("order", models.PositiveSmallIntegerField()), + ("scope", models.UUIDField(max_length=32, null=True)), + ("slug", models.SlugField()), ( - 'content_type', + "content_type", models.ForeignKey( - to='contenttypes.ContentType', - to_field='id', + to="contenttypes.ContentType", + to_field="id", on_delete=models.PROTECT, ), ), - ('object_id', models.UUIDField(max_length=32)), + ("object_id", models.UUIDField(max_length=32)), ], options={ - 'unique_together': set( - [('slug', 'scope'), ('content_type', 'object_id', 'order')] + "unique_together": set( + [("slug", "scope"), ("content_type", "object_id", "order")] ) }, bases=(models.Model,), ), migrations.CreateModel( - name='Tag', + name="Tag", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('tag', models.SlugField()), + ("tag", models.SlugField()), ( - 'source', + "source", models.PositiveSmallIntegerField( - choices=[(1, 'Feed'), (2, 'delicious'), (4, 'User')] + choices=[(1, "Feed"), (2, "delicious"), (4, "User")] ), ), ( - 'content_type', + "content_type", models.ForeignKey( - to='contenttypes.ContentType', - to_field='id', + to="contenttypes.ContentType", + to_field="id", on_delete=models.PROTECT, ), ), - ('object_id', models.UUIDField(max_length=32)), + ("object_id", models.UUIDField(max_length=32)), ], options={ - 'unique_together': set([('tag', 'source', 'content_type', 'object_id')]) + "unique_together": set([("tag", "source", "content_type", "object_id")]) }, bases=(models.Model,), ), migrations.CreateModel( - name='URL', + name="URL", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('order', models.PositiveSmallIntegerField()), - ('scope', models.UUIDField(max_length=32, null=True)), - ('url', models.URLField(max_length=1000)), + ("order", models.PositiveSmallIntegerField()), + ("scope", models.UUIDField(max_length=32, null=True)), + ("url", models.URLField(max_length=1000)), ( - 'content_type', + "content_type", models.ForeignKey( - to='contenttypes.ContentType', - to_field='id', + to="contenttypes.ContentType", + to_field="id", on_delete=models.PROTECT, ), ), - ('object_id', models.UUIDField(max_length=32)), + ("object_id", models.UUIDField(max_length=32)), ], options={ - 'unique_together': set( - [('url', 'scope'), ('content_type', 'object_id', 'order')] + "unique_together": set( + [("url", "scope"), ("content_type", "object_id", "order")] ) }, bases=(models.Model,), ), migrations.CreateModel( - name='Podcast', + name="Podcast", fields=[ ( - 'id', + "id", models.UUIDField(max_length=32, serialize=False, primary_key=True), ), - ('title', models.CharField(max_length=1000, blank=True)), - ('subtitle', models.CharField(max_length=1000, blank=True)), - ('description', models.TextField(blank=True)), - ('link', models.URLField(max_length=1000, null=True)), - ('language', models.CharField(max_length=10, null=True)), - ('last_update', models.DateTimeField()), - ('created', models.DateTimeField()), - ('modified', models.DateTimeField(auto_now=True)), - ('license', models.CharField(max_length=100, null=True)), - ('flattr_url', models.URLField(max_length=1000, null=True)), - ('content_types', models.CharField(max_length=20, blank=True)), - ('outdated', models.BooleanField(default=False)), - ('author', models.CharField(max_length=100, null=True, blank=True)), - ('logo_url', models.URLField(max_length=1000, null=True)), + ("title", models.CharField(max_length=1000, blank=True)), + ("subtitle", models.CharField(max_length=1000, blank=True)), + ("description", models.TextField(blank=True)), + ("link", models.URLField(max_length=1000, null=True)), + ("language", models.CharField(max_length=10, null=True)), + ("last_update", models.DateTimeField()), + ("created", models.DateTimeField()), + ("modified", models.DateTimeField(auto_now=True)), + ("license", models.CharField(max_length=100, null=True)), + ("flattr_url", models.URLField(max_length=1000, null=True)), + ("content_types", models.CharField(max_length=20, blank=True)), + ("outdated", models.BooleanField(default=False)), + ("author", models.CharField(max_length=100, null=True, blank=True)), + ("logo_url", models.URLField(max_length=1000, null=True)), ( - 'group', + "group", models.ForeignKey( - to='podcasts.PodcastGroup', - to_field='id', + to="podcasts.PodcastGroup", + to_field="id", null=True, on_delete=models.PROTECT, ), ), - ('group_member_name', models.CharField(max_length=30, null=True)), - ('common_episode_title', models.CharField(max_length=50, blank=True)), - ('new_location', models.URLField(max_length=1000, null=True)), - ('latest_episode_timestamp', models.DateTimeField(null=True)), - ('episode_count', models.PositiveIntegerField()), - ('hub', models.URLField(null=True)), - ('related_podcasts', models.ManyToManyField(to='self')), + ("group_member_name", models.CharField(max_length=30, null=True)), + ("common_episode_title", models.CharField(max_length=50, blank=True)), + ("new_location", models.URLField(max_length=1000, null=True)), + ("latest_episode_timestamp", models.DateTimeField(null=True)), + ("episode_count", models.PositiveIntegerField()), + ("hub", models.URLField(null=True)), + ("related_podcasts", models.ManyToManyField(to="self")), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='Episode', + name="Episode", fields=[ ( - 'id', + "id", models.UUIDField(max_length=32, serialize=False, primary_key=True), ), - ('title', models.CharField(max_length=1000, blank=True)), - ('subtitle', models.CharField(max_length=1000, blank=True)), - ('description', models.TextField(blank=True)), - ('link', models.URLField(max_length=1000, null=True)), - ('language', models.CharField(max_length=10, null=True)), - ('last_update', models.DateTimeField()), - ('created', models.DateTimeField()), - ('modified', models.DateTimeField(auto_now=True)), - ('license', models.CharField(max_length=100, null=True)), - ('flattr_url', models.URLField(max_length=1000, null=True)), - ('content_types', models.CharField(max_length=20, blank=True)), - ('outdated', models.BooleanField(default=False)), - ('author', models.CharField(max_length=100, null=True, blank=True)), - ('guid', models.CharField(max_length=50, null=True)), - ('content', models.TextField()), - ('released', models.DateTimeField(null=True)), - ('duration', models.PositiveIntegerField(null=True)), - ('filesize', models.PositiveIntegerField(null=True)), - ('mimetypes', models.CharField(max_length=50)), - ('listeners', models.PositiveIntegerField(null=True)), + ("title", models.CharField(max_length=1000, blank=True)), + ("subtitle", models.CharField(max_length=1000, blank=True)), + ("description", models.TextField(blank=True)), + ("link", models.URLField(max_length=1000, null=True)), + ("language", models.CharField(max_length=10, null=True)), + ("last_update", models.DateTimeField()), + ("created", models.DateTimeField()), + ("modified", models.DateTimeField(auto_now=True)), + ("license", models.CharField(max_length=100, null=True)), + ("flattr_url", models.URLField(max_length=1000, null=True)), + ("content_types", models.CharField(max_length=20, blank=True)), + ("outdated", models.BooleanField(default=False)), + ("author", models.CharField(max_length=100, null=True, blank=True)), + ("guid", models.CharField(max_length=50, null=True)), + ("content", models.TextField()), + ("released", models.DateTimeField(null=True)), + ("duration", models.PositiveIntegerField(null=True)), + ("filesize", models.PositiveIntegerField(null=True)), + ("mimetypes", models.CharField(max_length=50)), + ("listeners", models.PositiveIntegerField(null=True)), ( - 'podcast', + "podcast", models.ForeignKey( - to='podcasts.Podcast', to_field='id', on_delete=models.PROTECT + to="podcasts.Podcast", to_field="id", on_delete=models.PROTECT ), ), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ), ] diff --git a/mygpo/podcasts/migrations/0002_auto_20140609_0916.py b/mygpo/podcasts/migrations/0002_auto_20140609_0916.py index 6291604d4..2d6b83bd6 100644 --- a/mygpo/podcasts/migrations/0002_auto_20140609_0916.py +++ b/mygpo/podcasts/migrations/0002_auto_20140609_0916.py @@ -6,60 +6,60 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0001_initial')] + dependencies = [("podcasts", "0001_initial")] operations = [ migrations.AddField( - model_name='podcast', - name='twitter', + model_name="podcast", + name="twitter", field=models.CharField(max_length=15, null=True), preserve_default=True, ), migrations.AddField( - model_name='podcast', - name='restrictions', + model_name="podcast", + name="restrictions", field=models.CharField(max_length=20, null=True, blank=True), preserve_default=True, ), migrations.AlterField( - model_name='podcast', - name='episode_count', + model_name="podcast", + name="episode_count", field=models.PositiveIntegerField(default=0), ), migrations.AlterField( - model_name='episode', - name='last_update', + model_name="episode", + name="last_update", field=models.DateTimeField(null=True), ), migrations.AlterField( - model_name='podcastgroup', - name='title', + model_name="podcastgroup", + name="title", field=models.CharField(db_index=True, max_length=1000, blank=True), ), migrations.AlterField( - model_name='podcast', - name='last_update', + model_name="podcast", + name="last_update", field=models.DateTimeField(null=True), ), migrations.AlterField( - model_name='slug', name='slug', field=models.SlugField(max_length=150) + model_name="slug", name="slug", field=models.SlugField(max_length=150) ), migrations.AlterField( - model_name='episode', - name='title', + model_name="episode", + name="title", field=models.CharField(db_index=True, max_length=1000, blank=True), ), migrations.AlterField( - model_name='episode', - name='filesize', + model_name="episode", + name="filesize", field=models.BigIntegerField(null=True), ), migrations.AlterField( - model_name='podcast', - name='title', + model_name="podcast", + name="title", field=models.CharField(db_index=True, max_length=1000, blank=True), ), migrations.AlterField( - model_name='url', name='url', field=models.URLField(max_length=2048) + model_name="url", name="url", field=models.URLField(max_length=2048) ), ] diff --git a/mygpo/podcasts/migrations/0003_auto_20140610_1752.py b/mygpo/podcasts/migrations/0003_auto_20140610_1752.py index d89527032..c57b28603 100644 --- a/mygpo/podcasts/migrations/0003_auto_20140610_1752.py +++ b/mygpo/podcasts/migrations/0003_auto_20140610_1752.py @@ -6,37 +6,37 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0002_auto_20140609_0916')] + dependencies = [("podcasts", "0002_auto_20140609_0916")] operations = [ migrations.AlterField( - model_name='podcast', - name='license', + model_name="podcast", + name="license", field=models.CharField(max_length=100, null=True, db_index=True), ), migrations.AlterField( - model_name='podcast', - name='flattr_url', + model_name="podcast", + name="flattr_url", field=models.URLField(max_length=1000, null=True, db_index=True), ), migrations.AlterField( - model_name='episode', - name='flattr_url', + model_name="episode", + name="flattr_url", field=models.URLField(max_length=1000, null=True, db_index=True), ), migrations.AlterField( - model_name='podcast', - name='language', + model_name="podcast", + name="language", field=models.CharField(max_length=10, null=True, db_index=True), ), migrations.AlterField( - model_name='episode', - name='language', + model_name="episode", + name="language", field=models.CharField(max_length=10, null=True, db_index=True), ), migrations.AlterField( - model_name='episode', - name='license', + model_name="episode", + name="license", field=models.CharField(max_length=100, null=True, db_index=True), ), ] diff --git a/mygpo/podcasts/migrations/0004_auto_20140610_1800.py b/mygpo/podcasts/migrations/0004_auto_20140610_1800.py index 78b1551a2..29133c63d 100644 --- a/mygpo/podcasts/migrations/0004_auto_20140610_1800.py +++ b/mygpo/podcasts/migrations/0004_auto_20140610_1800.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0003_auto_20140610_1752')] + dependencies = [("podcasts", "0003_auto_20140610_1752")] operations = [ migrations.AlterField( - model_name='podcast', - name='author', + model_name="podcast", + name="author", field=models.CharField(max_length=150, null=True, blank=True), ), migrations.AlterField( - model_name='episode', - name='author', + model_name="episode", + name="author", field=models.CharField(max_length=150, null=True, blank=True), ), ] diff --git a/mygpo/podcasts/migrations/0005_auto_20140610_1854.py b/mygpo/podcasts/migrations/0005_auto_20140610_1854.py index 37aeeb6a9..e5af5b9c1 100644 --- a/mygpo/podcasts/migrations/0005_auto_20140610_1854.py +++ b/mygpo/podcasts/migrations/0005_auto_20140610_1854.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0004_auto_20140610_1800')] + dependencies = [("podcasts", "0004_auto_20140610_1800")] operations = [ migrations.AlterField( - model_name='slug', - name='scope', + model_name="slug", + name="scope", field=models.UUIDField(max_length=32, null=True, db_index=True), ), migrations.AlterField( - model_name='url', - name='scope', + model_name="url", + name="scope", field=models.UUIDField(max_length=32, null=True, db_index=True), ), ] diff --git a/mygpo/podcasts/migrations/0006_auto_20140614_0836.py b/mygpo/podcasts/migrations/0006_auto_20140614_0836.py index e088a5e79..e847ee240 100644 --- a/mygpo/podcasts/migrations/0006_auto_20140614_0836.py +++ b/mygpo/podcasts/migrations/0006_auto_20140614_0836.py @@ -6,22 +6,22 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0005_auto_20140610_1854')] + dependencies = [("podcasts", "0005_auto_20140610_1854")] operations = [ migrations.AlterField( - model_name='episode', - name='outdated', + model_name="episode", + name="outdated", field=models.BooleanField(default=False, db_index=True), ), migrations.AlterField( - model_name='podcast', - name='outdated', + model_name="podcast", + name="outdated", field=models.BooleanField(default=False, db_index=True), ), migrations.AlterField( - model_name='episode', - name='guid', + model_name="episode", + name="guid", field=models.CharField(max_length=100, null=True), ), ] diff --git a/mygpo/podcasts/migrations/0007_auto_20140614_0846.py b/mygpo/podcasts/migrations/0007_auto_20140614_0846.py index 525cf24f5..8e285d1af 100644 --- a/mygpo/podcasts/migrations/0007_auto_20140614_0846.py +++ b/mygpo/podcasts/migrations/0007_auto_20140614_0846.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0006_auto_20140614_0836')] + dependencies = [("podcasts", "0006_auto_20140614_0836")] operations = [ migrations.AlterField( - model_name='episode', - name='guid', + model_name="episode", + name="guid", field=models.CharField(max_length=200, null=True), ) ] diff --git a/mygpo/podcasts/migrations/0008_auto_20140614_1152.py b/mygpo/podcasts/migrations/0008_auto_20140614_1152.py index 411565426..2bd8f26ce 100644 --- a/mygpo/podcasts/migrations/0008_auto_20140614_1152.py +++ b/mygpo/podcasts/migrations/0008_auto_20140614_1152.py @@ -6,18 +6,18 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0007_auto_20140614_0846')] + dependencies = [("podcasts", "0007_auto_20140614_0846")] operations = [ migrations.AlterField( - model_name='podcast', name='subtitle', field=models.TextField(blank=True) + model_name="podcast", name="subtitle", field=models.TextField(blank=True) ), migrations.AlterField( - model_name='podcastgroup', - name='subtitle', + model_name="podcastgroup", + name="subtitle", field=models.TextField(blank=True), ), migrations.AlterField( - model_name='episode', name='subtitle', field=models.TextField(blank=True) + model_name="episode", name="subtitle", field=models.TextField(blank=True) ), ] diff --git a/mygpo/podcasts/migrations/0009_auto_20140614_1231.py b/mygpo/podcasts/migrations/0009_auto_20140614_1231.py index 1bc80205a..f3e185b6b 100644 --- a/mygpo/podcasts/migrations/0009_auto_20140614_1231.py +++ b/mygpo/podcasts/migrations/0009_auto_20140614_1231.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0008_auto_20140614_1152')] + dependencies = [("podcasts", "0008_auto_20140614_1152")] operations = [ migrations.AlterField( - model_name='podcast', - name='author', + model_name="podcast", + name="author", field=models.CharField(max_length=200, null=True, blank=True), ), migrations.AlterField( - model_name='episode', - name='author', + model_name="episode", + name="author", field=models.CharField(max_length=200, null=True, blank=True), ), ] diff --git a/mygpo/podcasts/migrations/0010_auto_20140614_1232.py b/mygpo/podcasts/migrations/0010_auto_20140614_1232.py index 01bbb89fa..1de3b5d62 100644 --- a/mygpo/podcasts/migrations/0010_auto_20140614_1232.py +++ b/mygpo/podcasts/migrations/0010_auto_20140614_1232.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0009_auto_20140614_1231')] + dependencies = [("podcasts", "0009_auto_20140614_1231")] operations = [ migrations.AlterField( - model_name='podcast', - name='author', + model_name="podcast", + name="author", field=models.CharField(max_length=250, null=True, blank=True), ), migrations.AlterField( - model_name='episode', - name='author', + model_name="episode", + name="author", field=models.CharField(max_length=250, null=True, blank=True), ), ] diff --git a/mygpo/podcasts/migrations/0011_auto_20140614_1241.py b/mygpo/podcasts/migrations/0011_auto_20140614_1241.py index 49427b499..266dbf50a 100644 --- a/mygpo/podcasts/migrations/0011_auto_20140614_1241.py +++ b/mygpo/podcasts/migrations/0011_auto_20140614_1241.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0010_auto_20140614_1232')] + dependencies = [("podcasts", "0010_auto_20140614_1232")] operations = [ migrations.AlterField( - model_name='podcast', - name='common_episode_title', + model_name="podcast", + name="common_episode_title", field=models.CharField(max_length=100, blank=True), ) ] diff --git a/mygpo/podcasts/migrations/0012_podcast_update_interval.py b/mygpo/podcasts/migrations/0012_podcast_update_interval.py index 9770d7807..72f9ce7db 100644 --- a/mygpo/podcasts/migrations/0012_podcast_update_interval.py +++ b/mygpo/podcasts/migrations/0012_podcast_update_interval.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0011_auto_20140614_1241')] + dependencies = [("podcasts", "0011_auto_20140614_1241")] operations = [ migrations.AddField( - model_name='podcast', - name='update_interval', + model_name="podcast", + name="update_interval", field=models.PositiveSmallIntegerField(default=168), preserve_default=True, ) diff --git a/mygpo/podcasts/migrations/0013_auto_20140615_0903.py b/mygpo/podcasts/migrations/0013_auto_20140615_0903.py index 1d33febd3..b3a2c9165 100644 --- a/mygpo/podcasts/migrations/0013_auto_20140615_0903.py +++ b/mygpo/podcasts/migrations/0013_auto_20140615_0903.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0012_podcast_update_interval')] + dependencies = [("podcasts", "0012_podcast_update_interval")] operations = [ migrations.AlterField( - model_name='episode', - name='mimetypes', + model_name="episode", + name="mimetypes", field=models.CharField(max_length=100), ) ] diff --git a/mygpo/podcasts/migrations/0014_auto_20140615_1032.py b/mygpo/podcasts/migrations/0014_auto_20140615_1032.py index 2ead63e4b..37b9c40c2 100644 --- a/mygpo/podcasts/migrations/0014_auto_20140615_1032.py +++ b/mygpo/podcasts/migrations/0014_auto_20140615_1032.py @@ -7,17 +7,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0013_auto_20140615_0903')] + dependencies = [("podcasts", "0013_auto_20140615_0903")] operations = [ migrations.AlterField( - model_name='episode', - name='created', + model_name="episode", + name="created", field=models.DateTimeField(default=datetime.datetime.utcnow), ), migrations.AlterField( - model_name='podcast', - name='created', + model_name="podcast", + name="created", field=models.DateTimeField(default=datetime.datetime.utcnow), ), ] diff --git a/mygpo/podcasts/migrations/0015_auto_20140616_1105.py b/mygpo/podcasts/migrations/0015_auto_20140616_1105.py index 6509237c4..4b560624d 100644 --- a/mygpo/podcasts/migrations/0015_auto_20140616_1105.py +++ b/mygpo/podcasts/migrations/0015_auto_20140616_1105.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0016_restrictions_notnull')] + dependencies = [("podcasts", "0016_restrictions_notnull")] operations = [ migrations.AlterField( - model_name='podcast', - name='author', + model_name="podcast", + name="author", field=models.CharField(max_length=350, null=True, blank=True), ), migrations.AlterField( - model_name='episode', - name='author', + model_name="episode", + name="author", field=models.CharField(max_length=350, null=True, blank=True), ), ] diff --git a/mygpo/podcasts/migrations/0015_auto_20140616_2126.py b/mygpo/podcasts/migrations/0015_auto_20140616_2126.py index 5b87ee30d..1db9625e9 100644 --- a/mygpo/podcasts/migrations/0015_auto_20140616_2126.py +++ b/mygpo/podcasts/migrations/0015_auto_20140616_2126.py @@ -5,39 +5,39 @@ def set_scope(apps, schema_editor): - URL = apps.get_model('podcasts', 'URL') - Slug = apps.get_model('podcasts', 'Slug') + URL = apps.get_model("podcasts", "URL") + Slug = apps.get_model("podcasts", "Slug") - URL.objects.filter(scope__isnull=True).update(scope='') - Slug.objects.filter(scope__isnull=True).update(scope='') + URL.objects.filter(scope__isnull=True).update(scope="") + Slug.objects.filter(scope__isnull=True).update(scope="") class Migration(migrations.Migration): - dependencies = [('podcasts', '0014_auto_20140615_1032')] + dependencies = [("podcasts", "0014_auto_20140615_1032")] operations = [ migrations.AlterField( - model_name='slug', - name='scope', + model_name="slug", + name="scope", field=models.CharField(db_index=True, max_length=32, blank=True, null=True), ), migrations.AlterField( - model_name='url', - name='scope', + model_name="url", + name="scope", field=models.CharField(db_index=True, max_length=32, blank=True, null=True), ), migrations.RunPython(set_scope), migrations.AlterField( - model_name='slug', - name='scope', + model_name="slug", + name="scope", field=models.CharField( db_index=True, max_length=32, blank=True, null=False ), ), migrations.AlterField( - model_name='url', - name='scope', + model_name="url", + name="scope", field=models.CharField( db_index=True, max_length=32, blank=True, null=False ), diff --git a/mygpo/podcasts/migrations/0016_restrictions_notnull.py b/mygpo/podcasts/migrations/0016_restrictions_notnull.py index 814699867..4384a03df 100644 --- a/mygpo/podcasts/migrations/0016_restrictions_notnull.py +++ b/mygpo/podcasts/migrations/0016_restrictions_notnull.py @@ -5,21 +5,21 @@ def set_restriction(apps, schema_editor): - Podcast = apps.get_model('podcasts', 'Podcast') + Podcast = apps.get_model("podcasts", "Podcast") for podcast in Podcast.objects.filter(restrictions__isnull=True): - podcast.restrictions = '' + podcast.restrictions = "" podcast.save() class Migration(migrations.Migration): - dependencies = [('podcasts', '0015_auto_20140616_2126')] + dependencies = [("podcasts", "0015_auto_20140616_2126")] operations = [ migrations.RunPython(set_restriction), migrations.AlterField( - model_name='podcast', - name='restrictions', - field=models.CharField(default='', max_length=20, blank=True), + model_name="podcast", + name="restrictions", + field=models.CharField(default="", max_length=20, blank=True), ), ] diff --git a/mygpo/podcasts/migrations/0017_podcast_subscribers.py b/mygpo/podcasts/migrations/0017_podcast_subscribers.py index 153dd876d..d25a898c2 100644 --- a/mygpo/podcasts/migrations/0017_podcast_subscribers.py +++ b/mygpo/podcasts/migrations/0017_podcast_subscribers.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0015_auto_20140616_1105')] + dependencies = [("podcasts", "0015_auto_20140616_1105")] operations = [ migrations.AddField( - model_name='podcast', - name='subscribers', + model_name="podcast", + name="subscribers", field=models.PositiveIntegerField(default=0), preserve_default=True, ) diff --git a/mygpo/podcasts/migrations/0018_podcast_released.py b/mygpo/podcasts/migrations/0018_podcast_released.py index 7be8d5014..ae8b43cf4 100644 --- a/mygpo/podcasts/migrations/0018_podcast_released.py +++ b/mygpo/podcasts/migrations/0018_podcast_released.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0017_podcast_subscribers')] + dependencies = [("podcasts", "0017_podcast_subscribers")] operations = [ migrations.AlterField( - model_name='episode', - name='released', + model_name="episode", + name="released", field=models.DateTimeField(null=True, db_index=True), ) ] diff --git a/mygpo/podcasts/migrations/0019_ondelete.py b/mygpo/podcasts/migrations/0019_ondelete.py index ff8e62e75..2f82b9c21 100644 --- a/mygpo/podcasts/migrations/0019_ondelete.py +++ b/mygpo/podcasts/migrations/0019_ondelete.py @@ -7,62 +7,62 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0018_podcast_released')] + dependencies = [("podcasts", "0018_podcast_released")] operations = [ migrations.AlterField( - model_name='slug', - name='content_type', + model_name="slug", + name="content_type", field=models.ForeignKey( - to='contenttypes.ContentType', + to="contenttypes.ContentType", on_delete=django.db.models.deletion.PROTECT, - to_field='id', + to_field="id", ), ), migrations.AlterField( - model_name='podcast', - name='group', + model_name="podcast", + name="group", field=models.ForeignKey( - to='podcasts.PodcastGroup', + to="podcasts.PodcastGroup", on_delete=django.db.models.deletion.PROTECT, - to_field='id', + to_field="id", null=True, ), ), migrations.AlterField( - model_name='tag', - name='content_type', + model_name="tag", + name="content_type", field=models.ForeignKey( - to='contenttypes.ContentType', + to="contenttypes.ContentType", on_delete=django.db.models.deletion.PROTECT, - to_field='id', + to_field="id", ), ), migrations.AlterField( - model_name='mergeduuid', - name='content_type', + model_name="mergeduuid", + name="content_type", field=models.ForeignKey( - to='contenttypes.ContentType', + to="contenttypes.ContentType", on_delete=django.db.models.deletion.PROTECT, - to_field='id', + to_field="id", ), ), migrations.AlterField( - model_name='url', - name='content_type', + model_name="url", + name="content_type", field=models.ForeignKey( - to='contenttypes.ContentType', + to="contenttypes.ContentType", on_delete=django.db.models.deletion.PROTECT, - to_field='id', + to_field="id", ), ), migrations.AlterField( - model_name='episode', - name='podcast', + model_name="episode", + name="podcast", field=models.ForeignKey( - to='podcasts.Podcast', + to="podcasts.Podcast", on_delete=django.db.models.deletion.PROTECT, - to_field='id', + to_field="id", ), ), ] diff --git a/mygpo/podcasts/migrations/0020_extend_episode_mimetypes.py b/mygpo/podcasts/migrations/0020_extend_episode_mimetypes.py index d9a8ca162..a16fe756e 100644 --- a/mygpo/podcasts/migrations/0020_extend_episode_mimetypes.py +++ b/mygpo/podcasts/migrations/0020_extend_episode_mimetypes.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0019_ondelete')] + dependencies = [("podcasts", "0019_ondelete")] operations = [ migrations.AlterField( - model_name='episode', - name='mimetypes', + model_name="episode", + name="mimetypes", field=models.CharField(max_length=200), ) ] diff --git a/mygpo/podcasts/migrations/0021_meta.py b/mygpo/podcasts/migrations/0021_meta.py index 444f98904..62ed9ec4c 100644 --- a/mygpo/podcasts/migrations/0021_meta.py +++ b/mygpo/podcasts/migrations/0021_meta.py @@ -7,28 +7,28 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0020_extend_episode_mimetypes'), - ('contenttypes', '__first__'), + ("podcasts", "0020_extend_episode_mimetypes"), + ("contenttypes", "__first__"), ] operations = [ migrations.AlterModelOptions( - name='episode', options={'ordering': ['-released']} + name="episode", options={"ordering": ["-released"]} ), migrations.AlterModelOptions( - name='mergeduuid', + name="mergeduuid", options={ - 'verbose_name': 'Merged UUID', - 'verbose_name_plural': 'Merged UUIDs', + "verbose_name": "Merged UUID", + "verbose_name_plural": "Merged UUIDs", }, ), - migrations.AlterModelOptions(name='slug', options={'ordering': ['order']}), + migrations.AlterModelOptions(name="slug", options={"ordering": ["order"]}), migrations.AlterModelOptions( - name='url', + name="url", options={ - 'ordering': ['order'], - 'verbose_name': 'URL', - 'verbose_name_plural': 'URLs', + "ordering": ["order"], + "verbose_name": "URL", + "verbose_name_plural": "URLs", }, ), ] diff --git a/mygpo/podcasts/migrations/0022_index_episode_listeners.py b/mygpo/podcasts/migrations/0022_index_episode_listeners.py index 03a42eadc..af89366af 100644 --- a/mygpo/podcasts/migrations/0022_index_episode_listeners.py +++ b/mygpo/podcasts/migrations/0022_index_episode_listeners.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0021_meta')] + dependencies = [("podcasts", "0021_meta")] operations = [ migrations.AlterField( - model_name='episode', - name='listeners', + model_name="episode", + name="listeners", field=models.PositiveIntegerField(null=True, db_index=True), ) ] diff --git a/mygpo/podcasts/migrations/0023_auto_20140729_1711.py b/mygpo/podcasts/migrations/0023_auto_20140729_1711.py index 6239fb593..1454c8618 100644 --- a/mygpo/podcasts/migrations/0023_auto_20140729_1711.py +++ b/mygpo/podcasts/migrations/0023_auto_20140729_1711.py @@ -7,31 +7,31 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0022_index_episode_listeners'), ('auth', '__first__')] + dependencies = [("podcasts", "0022_index_episode_listeners"), ("auth", "__first__")] operations = [ migrations.AddField( - model_name='tag', - name='user', + model_name="tag", + name="user", field=models.ForeignKey( to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE ), preserve_default=True, ), migrations.AlterField( - model_name='episode', - name='created', + model_name="episode", + name="created", field=models.DateTimeField(auto_now_add=True), ), migrations.AlterField( - model_name='podcast', - name='created', + model_name="podcast", + name="created", field=models.DateTimeField(auto_now_add=True), ), migrations.AlterUniqueTogether( - name='tag', + name="tag", unique_together=set( - [('tag', 'source', 'user', 'content_type', 'object_id')] + [("tag", "source", "user", "content_type", "object_id")] ), ), ] diff --git a/mygpo/podcasts/migrations/0024_episodes_index.py b/mygpo/podcasts/migrations/0024_episodes_index.py index dd9f725f0..a51dbbb61 100644 --- a/mygpo/podcasts/migrations/0024_episodes_index.py +++ b/mygpo/podcasts/migrations/0024_episodes_index.py @@ -9,15 +9,15 @@ def forward(apps, schema_editor): # This index can apparently not be created on sqlite # As it is not recommended for production use, we can just # skip the index there - if schema_editor.connection.vendor == 'sqlite': + if schema_editor.connection.vendor == "sqlite": return migrations.RunSQL( sql=[ ( - 'CREATE INDEX episodes_podcast_hasreleased ' - 'ON podcasts_episode ' - '(podcast_id, (released IS NOT NULL) DESC, released DESC);', + "CREATE INDEX episodes_podcast_hasreleased " + "ON podcasts_episode " + "(podcast_id, (released IS NOT NULL) DESC, released DESC);", None, ) ] @@ -25,12 +25,12 @@ def forward(apps, schema_editor): def reverse(apps, schema_editor): - migrations.RunSQL([('DROP INDEX IF EXISTS episodes_podcast_hasreleased;', None)]) + migrations.RunSQL([("DROP INDEX IF EXISTS episodes_podcast_hasreleased;", None)]) class Migration(migrations.Migration): - dependencies = [('podcasts', '0023_auto_20140729_1711')] + dependencies = [("podcasts", "0023_auto_20140729_1711")] operations = [ # Wrap RunSQL in RunPython to check for DB backend diff --git a/mygpo/podcasts/migrations/0025_episode_index.py b/mygpo/podcasts/migrations/0025_episode_index.py index 3eba40c0f..7f071082d 100644 --- a/mygpo/podcasts/migrations/0025_episode_index.py +++ b/mygpo/podcasts/migrations/0025_episode_index.py @@ -6,10 +6,10 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0024_episodes_index')] + dependencies = [("podcasts", "0024_episodes_index")] operations = [ migrations.AlterIndexTogether( - name='episode', index_together=set([('podcast', 'outdated', 'released')]) + name="episode", index_together=set([("podcast", "outdated", "released")]) ) ] diff --git a/mygpo/podcasts/migrations/0026_slug_index.py b/mygpo/podcasts/migrations/0026_slug_index.py index f001621b4..9d6360d21 100644 --- a/mygpo/podcasts/migrations/0026_slug_index.py +++ b/mygpo/podcasts/migrations/0026_slug_index.py @@ -6,10 +6,10 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0025_episode_index')] + dependencies = [("podcasts", "0025_episode_index")] operations = [ migrations.AlterIndexTogether( - name='slug', index_together=set([('slug', 'content_type')]) + name="slug", index_together=set([("slug", "content_type")]) ) ] diff --git a/mygpo/podcasts/migrations/0027_episode_index.py b/mygpo/podcasts/migrations/0027_episode_index.py index 45b78cee9..d46740d3b 100644 --- a/mygpo/podcasts/migrations/0027_episode_index.py +++ b/mygpo/podcasts/migrations/0027_episode_index.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0026_slug_index')] + dependencies = [("podcasts", "0026_slug_index")] operations = [ migrations.AlterIndexTogether( - name='episode', + name="episode", index_together=set( - [('podcast', 'released'), ('podcast', 'outdated', 'released')] + [("podcast", "released"), ("podcast", "outdated", "released")] ), ) ] diff --git a/mygpo/podcasts/migrations/0028_episode_indexes.py b/mygpo/podcasts/migrations/0028_episode_indexes.py index acc1eee60..9358c0f95 100644 --- a/mygpo/podcasts/migrations/0028_episode_indexes.py +++ b/mygpo/podcasts/migrations/0028_episode_indexes.py @@ -6,23 +6,23 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0027_episode_index')] + dependencies = [("podcasts", "0027_episode_index")] operations = [ migrations.AlterField( - model_name='podcast', - name='related_podcasts', + model_name="podcast", + name="related_podcasts", field=models.ManyToManyField( - related_name='related_podcasts_rel_+', to='self' + related_name="related_podcasts_rel_+", to="self" ), ), migrations.AlterIndexTogether( - name='episode', + name="episode", index_together=set( [ - ('released', 'podcast'), - ('podcast', 'released'), - ('podcast', 'outdated', 'released'), + ("released", "podcast"), + ("podcast", "released"), + ("podcast", "outdated", "released"), ] ), ), diff --git a/mygpo/podcasts/migrations/0029_episode_index_toplist.py b/mygpo/podcasts/migrations/0029_episode_index_toplist.py index d80611533..f3f124a0d 100644 --- a/mygpo/podcasts/migrations/0029_episode_index_toplist.py +++ b/mygpo/podcasts/migrations/0029_episode_index_toplist.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0028_episode_indexes')] + dependencies = [("podcasts", "0028_episode_indexes")] operations = [ migrations.AlterIndexTogether( - name='episode', + name="episode", index_together=set( [ - ('language', 'listeners'), - ('released', 'podcast'), - ('podcast', 'released'), - ('podcast', 'outdated', 'released'), + ("language", "listeners"), + ("released", "podcast"), + ("podcast", "released"), + ("podcast", "outdated", "released"), ] ), ) diff --git a/mygpo/podcasts/migrations/0030_ordered_episode.py b/mygpo/podcasts/migrations/0030_ordered_episode.py index fdb02f566..582f3c455 100644 --- a/mygpo/podcasts/migrations/0030_ordered_episode.py +++ b/mygpo/podcasts/migrations/0030_ordered_episode.py @@ -4,13 +4,13 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0029_episode_index_toplist')] + dependencies = [("podcasts", "0029_episode_index_toplist")] operations = [ - migrations.AlterModelOptions(name='episode', options={'ordering': ['order']}), + migrations.AlterModelOptions(name="episode", options={"ordering": ["order"]}), migrations.AddField( - model_name='episode', - name='order', + model_name="episode", + name="order", field=models.PositiveIntegerField(default=None, null=True), preserve_default=True, ), diff --git a/mygpo/podcasts/migrations/0031_podcast_max_episode_order.py b/mygpo/podcasts/migrations/0031_podcast_max_episode_order.py index be0d46207..0e78bace2 100644 --- a/mygpo/podcasts/migrations/0031_podcast_max_episode_order.py +++ b/mygpo/podcasts/migrations/0031_podcast_max_episode_order.py @@ -4,12 +4,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0030_ordered_episode')] + dependencies = [("podcasts", "0030_ordered_episode")] operations = [ migrations.AddField( - model_name='podcast', - name='max_episode_order', + model_name="podcast", + name="max_episode_order", field=models.PositiveIntegerField(default=None, null=True), preserve_default=True, ) diff --git a/mygpo/podcasts/migrations/0032_episode_order_bigint.py b/mygpo/podcasts/migrations/0032_episode_order_bigint.py index 2035538ce..b6e0036fd 100644 --- a/mygpo/podcasts/migrations/0032_episode_order_bigint.py +++ b/mygpo/podcasts/migrations/0032_episode_order_bigint.py @@ -4,15 +4,15 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0031_podcast_max_episode_order')] + dependencies = [("podcasts", "0031_podcast_max_episode_order")] operations = [ migrations.AlterModelOptions( - name='episode', options={'ordering': ['-released']} + name="episode", options={"ordering": ["-released"]} ), migrations.AlterField( - model_name='episode', - name='order', + model_name="episode", + name="order", field=models.BigIntegerField(default=None, null=True), preserve_default=True, ), diff --git a/mygpo/podcasts/migrations/0033_duration_biginteger.py b/mygpo/podcasts/migrations/0033_duration_biginteger.py index fa517e327..88b15c2bc 100644 --- a/mygpo/podcasts/migrations/0033_duration_biginteger.py +++ b/mygpo/podcasts/migrations/0033_duration_biginteger.py @@ -4,12 +4,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0032_episode_order_bigint')] + dependencies = [("podcasts", "0032_episode_order_bigint")] operations = [ migrations.AlterField( - model_name='episode', - name='duration', + model_name="episode", + name="duration", field=models.BigIntegerField(null=True), preserve_default=True, ) diff --git a/mygpo/podcasts/migrations/0034_episode_ordering.py b/mygpo/podcasts/migrations/0034_episode_ordering.py index 65157385c..42030f5f4 100644 --- a/mygpo/podcasts/migrations/0034_episode_ordering.py +++ b/mygpo/podcasts/migrations/0034_episode_ordering.py @@ -4,21 +4,21 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0033_duration_biginteger')] + dependencies = [("podcasts", "0033_duration_biginteger")] operations = [ migrations.AlterModelOptions( - name='episode', options={'ordering': ['-order', '-released']} + name="episode", options={"ordering": ["-order", "-released"]} ), migrations.AlterIndexTogether( - name='episode', + name="episode", index_together=set( [ - ('podcast', 'order', 'released'), - ('released', 'podcast'), - ('podcast', 'released'), - ('language', 'listeners'), - ('podcast', 'outdated', 'released'), + ("podcast", "order", "released"), + ("released", "podcast"), + ("podcast", "released"), + ("language", "listeners"), + ("podcast", "outdated", "released"), ] ), ), diff --git a/mygpo/podcasts/migrations/0035_django_uuidfield.py b/mygpo/podcasts/migrations/0035_django_uuidfield.py index e67275235..fe475759f 100644 --- a/mygpo/podcasts/migrations/0035_django_uuidfield.py +++ b/mygpo/podcasts/migrations/0035_django_uuidfield.py @@ -4,37 +4,37 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0034_episode_ordering')] + dependencies = [("podcasts", "0034_episode_ordering")] operations = [ migrations.AlterField( - model_name='episode', - name='id', + model_name="episode", + name="id", field=models.UUIDField(serialize=False, primary_key=True), ), migrations.AlterField( - model_name='mergeduuid', name='object_id', field=models.UUIDField() + model_name="mergeduuid", name="object_id", field=models.UUIDField() ), migrations.AlterField( - model_name='mergeduuid', name='uuid', field=models.UUIDField(unique=True) + model_name="mergeduuid", name="uuid", field=models.UUIDField(unique=True) ), migrations.AlterField( - model_name='podcast', - name='id', + model_name="podcast", + name="id", field=models.UUIDField(serialize=False, primary_key=True), ), migrations.AlterField( - model_name='podcastgroup', - name='id', + model_name="podcastgroup", + name="id", field=models.UUIDField(serialize=False, primary_key=True), ), migrations.AlterField( - model_name='slug', name='object_id', field=models.UUIDField() + model_name="slug", name="object_id", field=models.UUIDField() ), migrations.AlterField( - model_name='tag', name='object_id', field=models.UUIDField() + model_name="tag", name="object_id", field=models.UUIDField() ), migrations.AlterField( - model_name='url', name='object_id', field=models.UUIDField() + model_name="url", name="object_id", field=models.UUIDField() ), ] diff --git a/mygpo/podcasts/migrations/0036_related_podcasts.py b/mygpo/podcasts/migrations/0036_related_podcasts.py index f6f093c3a..b30c47f81 100644 --- a/mygpo/podcasts/migrations/0036_related_podcasts.py +++ b/mygpo/podcasts/migrations/0036_related_podcasts.py @@ -7,14 +7,14 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0035_django_uuidfield')] + dependencies = [("podcasts", "0035_django_uuidfield")] operations = [ migrations.AlterField( - model_name='podcast', - name='related_podcasts', + model_name="podcast", + name="related_podcasts", field=models.ManyToManyField( - related_name='_podcast_related_podcasts_+', to='podcasts.Podcast' + related_name="_podcast_related_podcasts_+", to="podcasts.Podcast" ), ) ] diff --git a/mygpo/podcasts/migrations/0037_index_podcast_lastupdate.py b/mygpo/podcasts/migrations/0037_index_podcast_lastupdate.py index ec60245ae..775a4d548 100644 --- a/mygpo/podcasts/migrations/0037_index_podcast_lastupdate.py +++ b/mygpo/podcasts/migrations/0037_index_podcast_lastupdate.py @@ -7,10 +7,10 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0036_related_podcasts')] + dependencies = [("podcasts", "0036_related_podcasts")] operations = [ migrations.AlterIndexTogether( - name='podcast', index_together=set([('last_update',)]) + name="podcast", index_together=set([("last_update",)]) ) ] diff --git a/mygpo/podcasts/migrations/0038_podcast_search_vector.py b/mygpo/podcasts/migrations/0038_podcast_search_vector.py index 490385bb9..986883f23 100644 --- a/mygpo/podcasts/migrations/0038_podcast_search_vector.py +++ b/mygpo/podcasts/migrations/0038_podcast_search_vector.py @@ -8,12 +8,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0037_index_podcast_lastupdate')] + dependencies = [("podcasts", "0037_index_podcast_lastupdate")] operations = [ migrations.AddField( - model_name='podcast', - name='search_vector', + model_name="podcast", + name="search_vector", field=SearchVectorField(null=True), ) ] diff --git a/mygpo/podcasts/migrations/0039_podcast_search_index_uptodate.py b/mygpo/podcasts/migrations/0039_podcast_search_index_uptodate.py index 8c974018d..55b622634 100644 --- a/mygpo/podcasts/migrations/0039_podcast_search_index_uptodate.py +++ b/mygpo/podcasts/migrations/0039_podcast_search_index_uptodate.py @@ -7,12 +7,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0038_podcast_search_vector')] + dependencies = [("podcasts", "0038_podcast_search_vector")] operations = [ migrations.AddField( - model_name='podcast', - name='search_index_uptodate', + model_name="podcast", + name="search_index_uptodate", field=models.BooleanField(db_index=True, default=False), ) ] diff --git a/mygpo/podcasts/migrations/0040_podcast_update_interval_factor.py b/mygpo/podcasts/migrations/0040_podcast_update_interval_factor.py index 1f7641034..e54fa8371 100644 --- a/mygpo/podcasts/migrations/0040_podcast_update_interval_factor.py +++ b/mygpo/podcasts/migrations/0040_podcast_update_interval_factor.py @@ -7,12 +7,12 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0039_podcast_search_index_uptodate')] + dependencies = [("podcasts", "0039_podcast_search_index_uptodate")] operations = [ migrations.AddField( - model_name='podcast', - name='update_interval_factor', + model_name="podcast", + name="update_interval_factor", field=models.FloatField(default=1), ) ] diff --git a/mygpo/podcasts/migrations/0041_auto_20191230_2319.py b/mygpo/podcasts/migrations/0041_auto_20191230_2319.py index 8de8a82fc..314599f70 100644 --- a/mygpo/podcasts/migrations/0041_auto_20191230_2319.py +++ b/mygpo/podcasts/migrations/0041_auto_20191230_2319.py @@ -6,65 +6,65 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0040_podcast_update_interval_factor'), + ("podcasts", "0040_podcast_update_interval_factor"), ] operations = [ migrations.AlterField( - model_name='episode', - name='flattr_url', + model_name="episode", + name="flattr_url", field=models.URLField( blank=True, db_index=True, max_length=1000, null=True ), ), migrations.AlterField( - model_name='episode', - name='license', + model_name="episode", + name="license", field=models.CharField( blank=True, db_index=True, max_length=100, null=True ), ), migrations.AlterField( - model_name='podcast', - name='flattr_url', + model_name="podcast", + name="flattr_url", field=models.URLField( blank=True, db_index=True, max_length=1000, null=True ), ), migrations.AlterField( - model_name='podcast', - name='group_member_name', + model_name="podcast", + name="group_member_name", field=models.CharField(blank=True, max_length=30, null=True), ), migrations.AlterField( - model_name='podcast', - name='hub', + model_name="podcast", + name="hub", field=models.URLField(blank=True, null=True), ), migrations.AlterField( - model_name='podcast', - name='license', + model_name="podcast", + name="license", field=models.CharField( blank=True, db_index=True, max_length=100, null=True ), ), migrations.AlterField( - model_name='podcast', - name='new_location', + model_name="podcast", + name="new_location", field=models.URLField(blank=True, max_length=1000, null=True), ), migrations.AlterField( - model_name='podcast', - name='related_podcasts', + model_name="podcast", + name="related_podcasts", field=models.ManyToManyField( blank=True, - related_name='_podcast_related_podcasts_+', - to='podcasts.Podcast', + related_name="_podcast_related_podcasts_+", + to="podcasts.Podcast", ), ), migrations.AlterField( - model_name='podcast', - name='twitter', + model_name="podcast", + name="twitter", field=models.CharField(blank=True, max_length=15, null=True), ), ] diff --git a/mygpo/podcasts/migrations/0042_auto_20191230_2322.py b/mygpo/podcasts/migrations/0042_auto_20191230_2322.py index adc6999a2..27a30d4e9 100644 --- a/mygpo/podcasts/migrations/0042_auto_20191230_2322.py +++ b/mygpo/podcasts/migrations/0042_auto_20191230_2322.py @@ -7,18 +7,18 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0041_auto_20191230_2319'), + ("podcasts", "0041_auto_20191230_2319"), ] operations = [ migrations.AlterField( - model_name='podcast', - name='group', + model_name="podcast", + name="group", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, - to='podcasts.PodcastGroup', + to="podcasts.PodcastGroup", ), ), ] diff --git a/mygpo/podcasts/migrations/0043_auto_20191230_2323.py b/mygpo/podcasts/migrations/0043_auto_20191230_2323.py index 245311539..fc9ff3779 100644 --- a/mygpo/podcasts/migrations/0043_auto_20191230_2323.py +++ b/mygpo/podcasts/migrations/0043_auto_20191230_2323.py @@ -6,18 +6,18 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0042_auto_20191230_2322'), + ("podcasts", "0042_auto_20191230_2322"), ] operations = [ migrations.AlterField( - model_name='episode', - name='language', + model_name="episode", + name="language", field=models.CharField(blank=True, db_index=True, max_length=10, null=True), ), migrations.AlterField( - model_name='podcast', - name='language', + model_name="podcast", + name="language", field=models.CharField(blank=True, db_index=True, max_length=10, null=True), ), ] diff --git a/mygpo/podcasts/migrations/0044_auto_20191230_2325.py b/mygpo/podcasts/migrations/0044_auto_20191230_2325.py index 3f09ed0bc..1dbbff189 100644 --- a/mygpo/podcasts/migrations/0044_auto_20191230_2325.py +++ b/mygpo/podcasts/migrations/0044_auto_20191230_2325.py @@ -6,18 +6,18 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0043_auto_20191230_2323'), + ("podcasts", "0043_auto_20191230_2323"), ] operations = [ migrations.AlterField( - model_name='episode', - name='last_update', + model_name="episode", + name="last_update", field=models.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='podcast', - name='last_update', + model_name="podcast", + name="last_update", field=models.DateTimeField(blank=True, null=True), ), ] diff --git a/mygpo/podcasts/migrations/0045_auto_20191230_2330.py b/mygpo/podcasts/migrations/0045_auto_20191230_2330.py index 54ad1a07c..99c4553ab 100644 --- a/mygpo/podcasts/migrations/0045_auto_20191230_2330.py +++ b/mygpo/podcasts/migrations/0045_auto_20191230_2330.py @@ -6,23 +6,23 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0044_auto_20191230_2325'), + ("podcasts", "0044_auto_20191230_2325"), ] operations = [ migrations.AlterField( - model_name='podcast', - name='episode_count', + model_name="podcast", + name="episode_count", field=models.PositiveIntegerField(blank=True, default=0), ), migrations.AlterField( - model_name='podcast', - name='latest_episode_timestamp', + model_name="podcast", + name="latest_episode_timestamp", field=models.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='podcast', - name='max_episode_order', + model_name="podcast", + name="max_episode_order", field=models.PositiveIntegerField(blank=True, default=None, null=True), ), ] diff --git a/mygpo/podcasts/models.py b/mygpo/podcasts/models.py index ecfcdc0c2..00b3fee09 100644 --- a/mygpo/podcasts/models.py +++ b/mygpo/podcasts/models.py @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) -GetCreateResult = collections.namedtuple('GetCreateResult', 'object created') +GetCreateResult = collections.namedtuple("GetCreateResult", "object created") # default podcast update interval in hours @@ -150,17 +150,17 @@ def get_by_any_id(self, id): class TagsMixin(models.Model): """ Methods for working with Tag objects """ - tags = GenericRelation('Tag', related_query_name='tags') + tags = GenericRelation("Tag", related_query_name="tags") class Meta: abstract = True class ScopedModel(models.Model): - """ A model that belongs to some scope, usually for limited uniqueness + """A model that belongs to some scope, usually for limited uniqueness scope does not allow null values, because null is not equal to null in SQL. - It could therefore not be used in unique constraints. """ + It could therefore not be used in unique constraints.""" # A slug / URL is unique within a scope; no two podcasts can have the same # URL (scope ''), and no two episdoes of the same podcast (scope = @@ -173,14 +173,14 @@ class Meta: def get_default_scope(self): """ Returns the default scope of the object """ raise NotImplementedError( - '{cls} should implement get_default_scope'.format( + "{cls} should implement get_default_scope".format( cls=self.__class__.__name__ ) ) class Slug(OrderedModel, ScopedModel): - """ Slug for any kind of Model + """Slug for any kind of Model Slugs are ordered, and the first slug is considered the canonical one. See also :class:`SlugsMixin` @@ -191,22 +191,22 @@ class Slug(OrderedModel, ScopedModel): # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") class Meta(OrderedModel.Meta): unique_together = ( # a slug is unique per type; eg a podcast can have the same slug # as an episode, but no two podcasts can have the same slug - ('slug', 'scope'), + ("slug", "scope"), # slugs of an object must be ordered, so that no two slugs of one # object have the same order key - ('content_type', 'object_id', 'order'), + ("content_type", "object_id", "order"), ) - index_together = [('slug', 'content_type')] + index_together = [("slug", "content_type")] def __repr__(self): - return '{cls}(slug={slug}, order={order}, content_object={obj}'.format( + return "{cls}(slug={slug}, order={order}, content_object={obj}".format( cls=self.__class__.__name__, slug=self.slug, order=self.order, @@ -217,16 +217,16 @@ def __repr__(self): class SlugsMixin(models.Model): """ Methods for working with Slug objects """ - slugs = GenericRelation(Slug, related_query_name='slugs') + slugs = GenericRelation(Slug, related_query_name="slugs") class Meta: abstract = True @property def slug(self): - """ The main slug of the podcast + """The main slug of the podcast - TODO: should be retrieved from a (materialized) view """ + TODO: should be retrieved from a (materialized) view""" # We could also use self.slugs.first() here, but this would result in a # different query and would render a .prefetch_related('slugs') useless @@ -234,7 +234,7 @@ def slug(self): # fetching all won't hurt slugs = list(self.slugs.all()) slug = slugs[0].slug if slugs else None - logger.debug('Found slugs %r, picking %r', slugs, slug) + logger.debug("Found slugs %r, picking %r", slugs, slug) return slug def add_slug(self, slug): @@ -246,7 +246,7 @@ def add_slug(self, slug): existing_slugs = self.slugs.all() # cut slug to the maximum allowed length - slug = utils.to_maxlength(Slug, 'slug', slug) + slug = utils.to_maxlength(Slug, "slug", slug) # check if slug already exists if slug in [s.slug for s in existing_slugs]: @@ -277,14 +277,14 @@ def remove_slug(self, slug): ).delete() def set_slugs(self, slugs): - """ Update the object's slugs to the given list + """Update the object's slugs to the given list 'slugs' should be a list of strings. Slugs that do not exist are created. Existing slugs that are not in the 'slugs' list are - deleted. """ - slugs = [utils.to_maxlength(Slug, 'slug', slug) for slug in slugs] + deleted.""" + slugs = [utils.to_maxlength(Slug, "slug", slug) for slug in slugs] existing = {s.slug: s for s in self.slugs.all()} - utils.set_ordered_entries(self, slugs, existing, Slug, 'slug', 'content_object') + utils.set_ordered_entries(self, slugs, existing, Slug, "slug", "content_object") class PodcastGroup(UUIDModel, TitleModel, SlugsMixin): @@ -293,7 +293,7 @@ class PodcastGroup(UUIDModel, TitleModel, SlugsMixin): @property def scope(self): """ A podcast group is always in the global scope """ - return '' + return "" def subscriber_count(self): # this could be done directly in the DB @@ -309,10 +309,10 @@ class PodcastQuerySet(MergedUUIDQuerySet): """ Custom queries for Podcasts """ def random(self): - """ Random podcasts + """Random podcasts Excludes podcasts with missing title to guarantee some - minimum quality of the results """ + minimum quality of the results""" # Using PostgreSQL's RANDOM() is very expensive, so we're generating a # random uuid and query podcasts with a higher ID @@ -321,7 +321,7 @@ def random(self): import uuid ruuid = uuid.uuid1() - return self.exclude(title='').filter(id__gt=ruuid) + return self.exclude(title="").filter(id__gt=ruuid) def license(self, license_url=None): """ Podcasts with any / the given license """ @@ -336,8 +336,8 @@ def order_by_next_update(self): "last_update + (update_interval * " "update_interval_factor || ' hours')::INTERVAL" ) - q = self.extra(select={'_next_update': NEXTUPDATE}) - return q.order_by('_next_update') + q = self.extra(select={"_next_update": NEXTUPDATE}) + return q.order_by("_next_update") @property def next_update(self): @@ -357,7 +357,7 @@ def toplist(self, language=None): if language: toplist = toplist.filter(language=language) - return toplist.order_by('-subscribers') + return toplist.order_by("-subscribers") class PodcastManager(GenericManager): @@ -369,30 +369,30 @@ def get_queryset(self): def get_advertised_podcast(self): """ Returns the currently advertised podcast """ if settings.PODCAST_AD_ID: - podcast = cache.get('podcast_ad') + podcast = cache.get("podcast_ad") if podcast: return podcast pk = uuid.UUID(settings.PODCAST_AD_ID) podcast = self.get_queryset().get(pk=pk) - cache.set('pocdast_ad', podcast) + cache.set("pocdast_ad", podcast) return podcast @transaction.atomic def get_or_create_for_url(self, url, defaults={}): if not url: - raise ValueError('The URL must not be empty') + raise ValueError("The URL must not be empty") # TODO: where to specify how uuid is created? import uuid - defaults.update({'id': uuid.uuid1()}) + defaults.update({"id": uuid.uuid1()}) - url = utils.to_maxlength(URL, 'url', url) + url = utils.to_maxlength(URL, "url", url) try: # try to fetch the podcast - podcast = Podcast.objects.get(urls__url=url, urls__scope='') + podcast = Podcast.objects.get(urls__url=url, urls__scope="") return GetCreateResult(podcast, False) except Podcast.DoesNotExist: @@ -401,39 +401,39 @@ def get_or_create_for_url(self, url, defaults={}): with transaction.atomic(): podcast = Podcast.objects.create(**defaults) url = URL.objects.create( - url=url, order=0, scope='', content_object=podcast + url=url, order=0, scope="", content_object=podcast ) return GetCreateResult(podcast, True) # URL could not be created, so it was created since the first get except IntegrityError: - podcast = Podcast.objects.get(urls__url=url, urls__scope='') + podcast = Podcast.objects.get(urls__url=url, urls__scope="") return GetCreateResult(podcast, False) class URL(OrderedModel, ScopedModel): - """ Podcasts and Episodes can have multiple URLs + """Podcasts and Episodes can have multiple URLs - URLs are ordered, and the first slug is considered the canonical one """ + URLs are ordered, and the first slug is considered the canonical one""" url = models.URLField(max_length=2048) # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") class Meta(OrderedModel.Meta): unique_together = ( # a URL is unique per scope - ('url', 'scope'), + ("url", "scope"), # URLs of an object must be ordered, so that no two slugs of one # object have the same order key - ('content_type', 'object_id', 'order'), + ("content_type", "object_id", "order"), ) - verbose_name = 'URL' - verbose_name_plural = 'URLs' + verbose_name = "URL" + verbose_name_plural = "URLs" def get_default_scope(self): return self.content_object.scope @@ -442,7 +442,7 @@ def get_default_scope(self): class UrlsMixin(models.Model): """ Methods for working with URL objects """ - urls = GenericRelation(URL, related_query_name='urls') + urls = GenericRelation(URL, related_query_name="urls") class Meta: abstract = True @@ -458,9 +458,9 @@ def url(self): return urls[0].url if urls else None def add_missing_urls(self, new_urls): - """ Adds missing URLS from new_urls + """Adds missing URLS from new_urls - The order of existing URLs is not changed """ + The order of existing URLs is not changed""" existing_urls = self.urls.all() next_order = max([-1] + [u.order for u in existing_urls]) + 1 existing_urls = [u.url for u in existing_urls] @@ -476,7 +476,7 @@ def add_missing_urls(self, new_urls): next_order += 1 except (IntegrityError, DataError) as ie: err = str(ie) - logger.warning(u'Could not add URL: {0}'.format(err)) + logger.warning(u"Could not add URL: {0}".format(err)) continue def set_url(self, url): @@ -490,18 +490,18 @@ def set_url(self, url): self.set_urls(urls) def set_urls(self, urls): - """ Update the object's URLS to the given list + """Update the object's URLS to the given list 'urls' should be a list of strings. Slugs that do not exist are created. Existing urls that are not in the 'urls' list are - deleted. """ - urls = [utils.to_maxlength(URL, 'url', url) for url in urls] + deleted.""" + urls = [utils.to_maxlength(URL, "url", url) for url in urls] existing = {u.url: u for u in self.urls.all()} - utils.set_ordered_entries(self, urls, existing, URL, 'url', 'content_object') + utils.set_ordered_entries(self, urls, existing, URL, "url", "content_object") class MergedUUID(models.Model): - """ If objects are merged their UUIDs are stored for later reference + """If objects are merged their UUIDs are stored for later reference see also :class:`MergedUUIDsMixin` """ @@ -511,17 +511,17 @@ class MergedUUID(models.Model): # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") class Meta: - verbose_name = 'Merged UUID' - verbose_name_plural = 'Merged UUIDs' + verbose_name = "Merged UUID" + verbose_name_plural = "Merged UUIDs" class MergedUUIDsMixin(models.Model): """ Methods for working with MergedUUID objects """ - merged_uuids = GenericRelation(MergedUUID, related_query_name='merged_uuids') + merged_uuids = GenericRelation(MergedUUID, related_query_name="merged_uuids") class Meta: abstract = True @@ -556,10 +556,10 @@ class Podcast( group_member_name = models.CharField(max_length=30, null=True, blank=True) # if p1 is related to p2, p2 is also related to p1 - related_podcasts = models.ManyToManyField('self', symmetrical=True, blank=True) + related_podcasts = models.ManyToManyField("self", symmetrical=True, blank=True) subscribers = models.PositiveIntegerField(default=0) - restrictions = models.CharField(max_length=20, null=False, blank=True, default='') + restrictions = models.CharField(max_length=20, null=False, blank=True, default="") common_episode_title = models.CharField(max_length=100, null=False, blank=True) new_location = models.URLField(max_length=1000, null=True, blank=True) latest_episode_timestamp = models.DateTimeField(null=True, blank=True) @@ -587,7 +587,7 @@ class Podcast( objects = PodcastManager() class Meta: - index_together = [('last_update',)] + index_together = [("last_update",)] def subscriber_count(self): # TODO: implement @@ -605,7 +605,7 @@ def group_with(self, other, grouptitle, myname, othername): group2 = other.group if group1 and group2: - raise ValueError('both podcasts already are in different groups') + raise ValueError("both podcasts already are in different groups") elif not (group1 or group2): # Form a new group @@ -655,7 +655,7 @@ def get_common_episode_title(self, num_episodes=100): # but consider only the part up to the first number. Otherwise we risk # removing part of the number (eg if a feed contains episodes 100-199) - common_title = re.search(r'^\D*', common_title).group(0) + common_title = re.search(r"^\D*", common_title).group(0) if len(common_title.strip()) < 2: return None @@ -675,7 +675,7 @@ def get_episode_after(self, episode): @property def scope(self): """ A podcast is always in the global scope """ - return '' + return "" @property def as_scope(self): @@ -690,18 +690,21 @@ def display_title(self): if not self.url: logger.warning( - 'Podcast with ID {podcast_id} does not have a URL'.format( + "Podcast with ID {podcast_id} does not have a URL".format( podcast_id=self.id ) ) - return _('Unknown Podcast') + return _("Unknown Podcast") return _( - 'Unknown Podcast from {domain}'.format(domain=utils.get_domain(self.url)) + "Unknown Podcast from {domain}".format(domain=utils.get_domain(self.url)) ) @property def next_update(self): + if not self.last_update: + return None + interval = timedelta(hours=self.update_interval) * self.update_interval_factor return self.last_update + interval @@ -714,7 +717,7 @@ def toplist(self, language=None): if language: toplist = toplist.filter(language=language) - return toplist.order_by('-listeners') + return toplist.order_by("-listeners") class EpisodeManager(GenericManager): @@ -724,17 +727,17 @@ def get_queryset(self): return EpisodeQuerySet(self.model, using=self._db) def get_or_create_for_url(self, podcast, url, defaults={}): - """ Create an Episode for a given URL + """Create an Episode for a given URL - This is the only place where new episodes are created """ + This is the only place where new episodes are created""" if not url: - raise ValueError('The URL must not be empty') + raise ValueError("The URL must not be empty") # TODO: where to specify how uuid is created? import uuid - url = utils.to_maxlength(URL, 'url', url) + url = utils.to_maxlength(URL, "url", url) try: url = URL.objects.get(url=url, scope=podcast.as_scope) @@ -770,7 +773,7 @@ def get_or_create_for_url(self, podcast, url, defaults={}): # recalculated when updating the podcast because counting # episodes can be very slow for podcasts with many episodes Podcast.objects.filter(pk=podcast.pk).update( - episode_count=F('episode_count') + 1 + episode_count=F("episode_count") + 1 ) return GetCreateResult(episode, True) @@ -816,15 +819,15 @@ class Episode( objects = EpisodeManager() class Meta: - ordering = ['-order', '-released'] + ordering = ["-order", "-released"] index_together = [ - ('podcast', 'outdated', 'released'), - ('podcast', 'released'), - ('released', 'podcast'), + ("podcast", "outdated", "released"), + ("podcast", "released"), + ("released", "podcast"), # index for typical episode toplist queries - ('language', 'listeners'), - ('podcast', 'order', 'released'), + ("language", "listeners"), + ("podcast", "order", "released"), ] @property @@ -842,8 +845,8 @@ def get_short_title(self, common_title): if not self.title or not common_title: return None - title = self.title.replace(common_title, '').strip() - title = re.sub(r'^[\W\d]+', '', title) + title = self.title.replace(common_title, "").strip() + title = re.sub(r"^[\W\d]+", "", title) return title def get_episode_number(self, common_title): @@ -851,8 +854,8 @@ def get_episode_number(self, common_title): if not self.title or not common_title: return None - title = self.title.replace(common_title, '').strip() - match = re.search(r'^\W*(\d+)', title) + title = self.title.replace(common_title, "").strip() + match = re.search(r"^\W*(\d+)", title) if not match: return None @@ -860,7 +863,7 @@ def get_episode_number(self, common_title): class Tag(models.Model): - """ Tags any kind of Model + """Tags any kind of Model See also :class:`TagsMixin` """ @@ -869,7 +872,7 @@ class Tag(models.Model): DELICIOUS = 2 USER = 4 - SOURCE_CHOICES = ((FEED, 'Feed'), (DELICIOUS, 'delicious'), (USER, 'User')) + SOURCE_CHOICES = ((FEED, "Feed"), (DELICIOUS, "delicious"), (USER, "User")) tag = models.SlugField() @@ -885,10 +888,10 @@ class Tag(models.Model): # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") class Meta: unique_together = ( # a tag can only be assigned once from one source to one item - ('tag', 'source', 'user', 'content_type', 'object_id'), + ("tag", "source", "user", "content_type", "object_id"), ) diff --git a/mygpo/podcasts/tests.py b/mygpo/podcasts/tests.py index 40e84a0e5..6f4982ddb 100644 --- a/mygpo/podcasts/tests.py +++ b/mygpo/podcasts/tests.py @@ -30,15 +30,15 @@ def test_next_update(self): def test_get_or_create_for_url(self): """ Test that get_or_create_for_url returns existing Podcast """ - URL = 'http://example.com/get_or_create.rss' + URL = "http://example.com/get_or_create.rss" p1 = Podcast.objects.get_or_create_for_url(URL).object p2 = Podcast.objects.get_or_create_for_url(URL).object self.assertEqual(p1.pk, p2.pk) def test_episode_count(self): """ Test if Podcast.episode_count is updated correctly """ - PODCAST_URL = 'http://example.com/podcast.rss' - EPISODE_URL = 'http://example.com/episode%d.mp3' + PODCAST_URL = "http://example.com/podcast.rss" + EPISODE_URL = "http://example.com/episode%d.mp3" NUM_EPISODES = 3 p = Podcast.objects.get_or_create_for_url(PODCAST_URL).object @@ -66,30 +66,30 @@ def test_group(self): self.podcast1 = create_podcast() self.podcast2 = create_podcast() - group = self.podcast1.group_with(self.podcast2, 'My Group', 'p1', 'p2') + group = self.podcast1.group_with(self.podcast2, "My Group", "p1", "p2") self.assertIn(self.podcast1, group.podcast_set.all()) self.assertIn(self.podcast2, group.podcast_set.all()) self.assertEqual(len(group.podcast_set.all()), 2) - self.assertEqual(group.title, 'My Group') - self.assertEqual(self.podcast1.group_member_name, 'p1') - self.assertEqual(self.podcast2.group_member_name, 'p2') + self.assertEqual(group.title, "My Group") + self.assertEqual(self.podcast1.group_member_name, "p1") + self.assertEqual(self.podcast2.group_member_name, "p2") # add to group self.podcast3 = create_podcast() - group = self.podcast1.group_with(self.podcast3, 'My Group', 'p1', 'p3') + group = self.podcast1.group_with(self.podcast3, "My Group", "p1", "p3") self.assertIn(self.podcast3, group.podcast_set.all()) - self.assertEqual(self.podcast3.group_member_name, 'p3') + self.assertEqual(self.podcast3.group_member_name, "p3") # add group to podcast self.podcast4 = create_podcast() - group = self.podcast4.group_with(self.podcast1, 'My Group', 'p4', 'p1') + group = self.podcast4.group_with(self.podcast1, "My Group", "p4", "p1") self.assertIn(self.podcast4, group.podcast_set.all()) - self.assertEqual(self.podcast4.group_member_name, 'p4') + self.assertEqual(self.podcast4.group_member_name, "p4") class SlugTests(TestCase): @@ -104,20 +104,20 @@ def test_update_slugs(self): with self.assertNumQueries(8): # set the canonical slug - podcast.set_slug('podcast-1') - self.assertEqual(podcast.slug, 'podcast-1') + podcast.set_slug("podcast-1") + self.assertEqual(podcast.slug, "podcast-1") with self.assertNumQueries(9): # set a new list of slugs - podcast.set_slugs(['podcast-2', 'podcast-1']) - self.assertEqual(podcast.slug, 'podcast-2') + podcast.set_slugs(["podcast-2", "podcast-1"]) + self.assertEqual(podcast.slug, "podcast-2") with self.assertNumQueries(2): # remove the canonical slug - podcast.remove_slug('podcast-2') - self.assertEqual(podcast.slug, 'podcast-1') + podcast.remove_slug("podcast-2") + self.assertEqual(podcast.slug, "podcast-1") with self.assertNumQueries(3): # add a non-canonical slug - podcast.add_slug('podcast-3') - self.assertEqual(podcast.slug, 'podcast-1') + podcast.add_slug("podcast-3") + self.assertEqual(podcast.slug, "podcast-1") diff --git a/mygpo/podcasts/urls.py b/mygpo/podcasts/urls.py index 1396da973..6ebedeb88 100644 --- a/mygpo/podcasts/urls.py +++ b/mygpo/podcasts/urls.py @@ -4,88 +4,88 @@ from mygpo.users import converters -register_converter(converters.ClientUIDConverter, 'client-uid') +register_converter(converters.ClientUIDConverter, "client-uid") podcast_uuid_patterns = [ - path('subscribe', podcast.subscribe_id, name='subscribe-id'), - path('subscribe/+all', podcast.subscribe_all_id, name='subscribe-all-id'), + path("subscribe", podcast.subscribe_id, name="subscribe-id"), + path("subscribe/+all", podcast.subscribe_all_id, name="subscribe-all-id"), path( - 'unsubscribe/', + "unsubscribe/", podcast.unsubscribe_id, - name='unsubscribe-id', + name="unsubscribe-id", ), - path('unsubscribe/+all', podcast.unsubscribe_all_id, name='unsubscribe-all-id'), - path('add-tag', podcast.add_tag_id, name='add-tag-id'), - path('remove-tag', podcast.remove_tag_id, name='remove-tag-id'), + path("unsubscribe/+all", podcast.unsubscribe_all_id, name="unsubscribe-all-id"), + path("add-tag", podcast.add_tag_id, name="add-tag-id"), + path("remove-tag", podcast.remove_tag_id, name="remove-tag-id"), path( - 'set-public', + "set-public", podcast.set_public_id, - name='podcast-public-id', - kwargs={'public': True}, + name="podcast-public-id", + kwargs={"public": True}, ), path( - 'set-private', + "set-private", podcast.set_public_id, - name='podcast-private-id', - kwargs={'public': False}, + name="podcast-private-id", + kwargs={"public": False}, ), - path('-episodes', podcast.all_episodes_id, name='podcast-all-episodes-id'), + path("-episodes", podcast.all_episodes_id, name="podcast-all-episodes-id"), ] podcast_slug_patterns = [ - path('subscribe', podcast.subscribe_slug, name='subscribe-slug'), - path('subscribe/+all', podcast.subscribe_all_slug, name='subscribe-all-slug'), + path("subscribe", podcast.subscribe_slug, name="subscribe-slug"), + path("subscribe/+all", podcast.subscribe_all_slug, name="subscribe-all-slug"), path( - 'unsubscribe/', + "unsubscribe/", podcast.unsubscribe_slug, - name='unsubscribe-slug', + name="unsubscribe-slug", ), - path('unsubscribe/+all', podcast.unsubscribe_all_slug, name='unsubscribe-all-slug'), - path('add-tag', podcast.add_tag_slug, name='add-tag-slug'), - path('remove-tag', podcast.remove_tag_slug, name='remove-tag-slug'), + path("unsubscribe/+all", podcast.unsubscribe_all_slug, name="unsubscribe-all-slug"), + path("add-tag", podcast.add_tag_slug, name="add-tag-slug"), + path("remove-tag", podcast.remove_tag_slug, name="remove-tag-slug"), path( - 'set-public', + "set-public", podcast.set_public_slug, - name='podcast-public-slug', - kwargs={'public': True}, + name="podcast-public-slug", + kwargs={"public": True}, ), path( - 'set-private', + "set-private", podcast.set_public_slug, - name='podcast-private-slug', - kwargs={'public': False}, + name="podcast-private-slug", + kwargs={"public": False}, ), - path('-episodes', podcast.all_episodes_slug, name='podcast-all-episodes-slug'), + path("-episodes", podcast.all_episodes_slug, name="podcast-all-episodes-slug"), ] episode_uuid_patterns = [ - path('toggle-favorite', episode.toggle_favorite_id, name='episode-fav-id'), - path('add-action', episode.add_action_id, name='add-episode-action-id'), - path('+history', episode.episode_history_id, name='episode-history-id'), + path("toggle-favorite", episode.toggle_favorite_id, name="episode-fav-id"), + path("add-action", episode.add_action_id, name="add-episode-action-id"), + path("+history", episode.episode_history_id, name="episode-history-id"), ] episode_slug_patterns = [ - path('toggle-favorite', episode.toggle_favorite_slug, name='episode-fav-slug'), - path('add-action', episode.add_action_slug, name='add-episode-action-slug'), - path('+history', episode.episode_history_slug, name='episode-history-slug'), + path("toggle-favorite", episode.toggle_favorite_slug, name="episode-fav-slug"), + path("add-action", episode.add_action_slug, name="add-episode-action-slug"), + path("+history", episode.episode_history_slug, name="episode-history-slug"), ] urlpatterns = [ - path('subscribe', podcast.subscribe_url, name='subscribe-by-url'), + path("subscribe", podcast.subscribe_url, name="subscribe-by-url"), # Podcast Views with UUIDs - path('podcast/', podcast.show_id, name='podcast-id'), - path('podcast//', include(podcast_uuid_patterns)), + path("podcast/", podcast.show_id, name="podcast-id"), + path("podcast//", include(podcast_uuid_patterns)), # Podcast Views with Slugs - path('podcast/', podcast.show_slug, name='podcast-slug'), - path('podcast//', include(podcast_slug_patterns)), - path('favorites/', episode.list_favorites, name='favorites'), + path("podcast/", podcast.show_slug, name="podcast-slug"), + path("podcast//", include(podcast_slug_patterns)), + path("favorites/", episode.list_favorites, name="favorites"), # Episodes for UUIDs - path('podcast//', episode.show_id, name='episode-id'), - path('podcast///', include(episode_uuid_patterns)), + path("podcast//", episode.show_id, name="episode-id"), + path("podcast///", include(episode_uuid_patterns)), # Episodes for Slugs - path('podcast//', episode.show_slug, name='episode-slug'), - path('podcast///', include(episode_slug_patterns)), + path("podcast//", episode.show_slug, name="episode-slug"), + path("podcast///", include(episode_slug_patterns)), ] diff --git a/mygpo/podcasts/views/episode.py b/mygpo/podcasts/views/episode.py index acb12344e..21bb8c2da 100644 --- a/mygpo/podcasts/views/episode.py +++ b/mygpo/podcasts/views/episode.py @@ -63,17 +63,17 @@ def episode(request, episode): return render( request, - 'episode.html', + "episode.html", { - 'episode': episode, - 'podcast': podcast, - 'prev': prev, - 'next': next, - 'has_history': has_history, - 'is_favorite': is_fav, - 'actions': EPISODE_ACTION_TYPES, - 'devices': devices, - 'is_publisher': is_publisher, + "episode": episode, + "podcast": podcast, + "prev": prev, + "next": next, + "has_history": has_history, + "is_favorite": is_fav, + "actions": EPISODE_ACTION_TYPES, + "devices": devices, + "is_publisher": is_publisher, }, ) @@ -90,13 +90,13 @@ def history(request, episode): history = ( EpisodeHistoryEntry.objects.filter(user=user, episode=episode) - .order_by('-timestamp') + .order_by("-timestamp") .prefetch_related( - 'episode', - 'episode__slugs', - 'episode__podcast', - 'episode__podcast__slugs', - 'client', + "episode", + "episode__slugs", + "episode__podcast", + "episode__podcast__slugs", + "client", ) ) @@ -104,13 +104,13 @@ def history(request, episode): return render( request, - 'episode-history.html', + "episode-history.html", { - 'episode': episode, - 'podcast': podcast, - 'history': history, - 'actions': EPISODE_ACTION_TYPES, - 'clients': clients, + "episode": episode, + "podcast": podcast, + "history": history, + "actions": EPISODE_ACTION_TYPES, + "clients": clients, }, ) @@ -150,13 +150,13 @@ def list_favorites(request): return render( request, - 'favorites.html', + "favorites.html", { - 'episodes': favorites, - 'feed_token': token, - 'site': site, - 'podcast': podcast, - 'recently_listened': recently_listened, + "episodes": favorites, + "feed_token": token, + "site": site, + "podcast": podcast, + "recently_listened": recently_listened, }, ) @@ -165,10 +165,10 @@ def list_favorites(request): def add_action(request, episode): user = request.user - client = user.client_set.get(id=request.POST.get('device')) + client = user.client_set.get(id=request.POST.get("device")) - action_str = request.POST.get('action') - timestamp = request.POST.get('timestamp', '') + action_str = request.POST.get("action") + timestamp = request.POST.get("timestamp", "") if timestamp: try: @@ -192,10 +192,10 @@ def slug_decorator(f): @wraps(f) def _decorator(request, p_slug, e_slug, *args, **kwargs): - pquery = Podcast.objects.filter(slugs__slug=p_slug, slugs__scope='') + pquery = Podcast.objects.filter(slugs__slug=p_slug, slugs__scope="") try: - podcast = pquery.prefetch_related('slugs').get() + podcast = pquery.prefetch_related("slugs").get() except Podcast.DoesNotExist: raise Http404 @@ -204,7 +204,7 @@ def _decorator(request, p_slug, e_slug, *args, **kwargs): ) try: - episode = equery.prefetch_related('urls', 'slugs').get() + episode = equery.prefetch_related("urls", "slugs").get() # set previously fetched podcast, to avoid additional query episode.podcast = podcast @@ -227,7 +227,7 @@ def _decorator(request, p_id, e_id, *args, **kwargs): try: query = Episode.objects.filter(id=e_id, podcast_id=p_id) - episode = query.select_related('podcast').get() + episode = query.select_related("podcast").get() except Episode.DoesNotExist: raise Http404 diff --git a/mygpo/podcasts/views/podcast.py b/mygpo/podcasts/views/podcast.py index e52831f74..2c1e5cff7 100644 --- a/mygpo/podcasts/views/podcast.py +++ b/mygpo/podcasts/views/podcast.py @@ -39,7 +39,7 @@ @vary_on_cookie @cache_control(private=True) -@allowed_methods(['GET']) +@allowed_methods(["GET"]) def show(request, podcast): """ Shows a podcast detail page """ @@ -67,7 +67,7 @@ def show(request, podcast): rel_podcasts = [] tags = get_tags(podcast, user) - has_tagged = any(t['is_own'] for t in tags) + has_tagged = any(t["is_own"] for t in tags) if user.is_authenticated: subscribed_devices = Client.objects.filter( @@ -91,41 +91,41 @@ def show(request, podcast): return render( request, - 'podcast.html', + "podcast.html", { - 'tags': tags, - 'has_tagged': has_tagged, - 'url': current_site, - 'has_history': has_history, - 'podcast': podcast, - 'devices': subscribed_devices, - 'related_podcasts': rel_podcasts, - 'can_subscribe': len(subscribe_targets) > 0, - 'subscribe_targets': subscribe_targets, - 'episode': episode, - 'episodes': episodes, - 'max_listeners': max_listeners, - 'is_publisher': is_publisher, - 'page_list': page_list, - 'current_page': 1, + "tags": tags, + "has_tagged": has_tagged, + "url": current_site, + "has_history": has_history, + "podcast": podcast, + "devices": subscribed_devices, + "related_podcasts": rel_podcasts, + "can_subscribe": len(subscribe_targets) > 0, + "subscribe_targets": subscribe_targets, + "episode": episode, + "episodes": episodes, + "max_listeners": max_listeners, + "is_publisher": is_publisher, + "page_list": page_list, + "current_page": 1, }, ) def get_tags(podcast, user, max_tags=50): - """ Returns all tags that user sees for the given podcast + """Returns all tags that user sees for the given podcast The tag list is a list of dicts in the form of {'tag': 'tech', 'is_own': - True}. "is_own" indicates if the tag was created by the given user. """ + True}. "is_own" indicates if the tag was created by the given user.""" tags = {} for tag in podcast.tags.all(): t = tag.tag.lower() if not t in tags: - tags[t] = {'tag': t, 'is_own': False} + tags[t] = {"tag": t, "is_own": False} if tag.user == user: - tags[t]['is_own'] = True + tags[t]["is_own"] = True return list(tags.values()) @@ -142,8 +142,8 @@ def episode_list(podcast, user, offset=0, limit=20): Episode.objects.filter( podcast=podcast, order__lte=page_start, order__gt=page_end ) - .prefetch_related('slugs') - .order_by('-order') + .prefetch_related("slugs") + .order_by("-order") ) @@ -151,7 +151,7 @@ def all_episodes(request, podcast, page_size=20): # Make sure page request is an int. If not, deliver first page. try: - page = int(request.GET.get('page', '1')) + page = int(request.GET.get("page", "1")) except ValueError: page = 1 @@ -169,14 +169,14 @@ def all_episodes(request, podcast, page_size=20): return render( request, - 'episodes.html', + "episodes.html", { - 'podcast': podcast, - 'episodes': episodes, - 'max_listeners': max_listeners, - 'page_list': page_list, - 'current_page': page, - 'is_publisher': is_publisher, + "podcast": podcast, + "episodes": episodes, + "max_listeners": max_listeners, + "page_list": page_list, + "current_page": page, + "is_publisher": is_publisher, }, ) @@ -185,13 +185,13 @@ def all_episodes(request, podcast, page_size=20): @login_required def add_tag(request, podcast): - tag_str = request.GET.get('tag', '') + tag_str = request.GET.get("tag", "") if not tag_str: return HttpResponseBadRequest() user = request.user - tags = tag_str.split(',') + tags = tag_str.split(",") tags = map(str.strip, tags) tags = map(str.lower, tags) tags = list(filter(None, tags)) @@ -201,7 +201,7 @@ def add_tag(request, podcast): for tag in tags: # trim to maximum length - tag = to_maxlength(Tag, 'tag', tag) + tag = to_maxlength(Tag, "tag", tag) Tag.objects.get_or_create( tag=tag, @@ -211,8 +211,8 @@ def add_tag(request, podcast): object_id=podcast.id, ) - if request.GET.get('next', '') == 'mytags': - return HttpResponseRedirect('/tags/') + if request.GET.get("next", "") == "mytags": + return HttpResponseRedirect("/tags/") return HttpResponseRedirect(get_podcast_link_target(podcast)) @@ -221,13 +221,13 @@ def add_tag(request, podcast): @login_required def remove_tag(request, podcast): - tag_str = request.GET.get('tag', None) + tag_str = request.GET.get("tag", None) if tag_str is None: return HttpResponseBadRequest() user = request.user - tags = tag_str.split(',') + tags = tag_str.split(",") tags = list(map(str.strip, tags)) ContentType.objects.get_for_model(podcast) @@ -241,32 +241,32 @@ def remove_tag(request, podcast): object_id=podcast.id, ).delete() - if request.GET.get('next', '') == 'mytags': - return HttpResponseRedirect('/tags/') + if request.GET.get("next", "") == "mytags": + return HttpResponseRedirect("/tags/") return HttpResponseRedirect(get_podcast_link_target(podcast)) @never_cache @login_required -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) def subscribe(request, podcast): - if request.method == 'POST': + if request.method == "POST": # multiple UIDs from the /podcast//subscribe device_uids = [k for (k, v) in request.POST.items() if k == v] # single UID from /podcast/ - if 'targets' in request.POST: - devices = request.POST.get('targets') - devices = devices.split(',') + if "targets" in request.POST: + devices = request.POST.get("targets") + devices = devices.split(",") device_uids.extend(devices) for uid in device_uids: try: device = request.user.client_set.get(uid=uid) - subscribe_podcast.delay(podcast, request.user, device) + subscribe_podcast.delay(podcast.pk, request.user.pk, device.uid) except Client.DoesNotExist as e: messages.error(request, str(e)) @@ -275,16 +275,16 @@ def subscribe(request, podcast): targets = get_subscribe_targets(podcast, request.user) - return render(request, 'subscribe.html', {'targets': targets, 'podcast': podcast}) + return render(request, "subscribe.html", {"targets": targets, "podcast": podcast}) @never_cache @login_required -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def subscribe_all(request, podcast): """ subscribe all of the user's devices to the podcast """ user = request.user - subscribe_podcast_all.delay(podcast, user) + subscribe_podcast_all.delay(podcast.pk, user.pk) return HttpResponseRedirect(get_podcast_link_target(podcast)) @@ -292,10 +292,10 @@ def subscribe_all(request, podcast): @login_required def unsubscribe(request, podcast, device_uid): - return_to = request.GET.get('return_to', None) + return_to = request.GET.get("return_to", None) if not return_to: - raise Http404('Wrong URL') + raise Http404("Wrong URL") user = request.user try: @@ -306,14 +306,14 @@ def unsubscribe(request, podcast, device_uid): return HttpResponseRedirect(return_to) try: - unsubscribe_podcast.delay(podcast, user, device) + unsubscribe_podcast.delay(podcast.pk, user.pk, device.uid) except SubscriptionException as e: logger.exception( - 'Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' + "Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s" % { - 'username': request.user.username, - 'podcast_url': podcast.url, - 'device_id': device.id, + "username": request.user.username, + "podcast_url": podcast.url, + "device_id": device.id, } ) @@ -322,36 +322,36 @@ def unsubscribe(request, podcast, device_uid): @never_cache @login_required -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def unsubscribe_all(request, podcast): """ unsubscribe all of the user's devices from the podcast """ user = request.user - unsubscribe_podcast_all.delay(podcast, user) + unsubscribe_podcast_all.delay(podcast.pk, user.pk) return HttpResponseRedirect(get_podcast_link_target(podcast)) @never_cache @login_required def subscribe_url(request): - url = request.GET.get('url', None) + url = request.GET.get("url", None) if not url: raise Http404( - 'http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml' + "http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml" ) url = normalize_feed_url(url) if not url: - raise Http404('Please specify a valid url') + raise Http404("Please specify a valid url") podcast = Podcast.objects.get_or_create_for_url(url).object - return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe')) + return HttpResponseRedirect(get_podcast_link_target(podcast, "subscribe")) @never_cache -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def set_public(request, podcast, public): settings, created = UserSettings.objects.get_or_create( user=request.user, @@ -377,7 +377,7 @@ def _decorator(request, slug, *args, **kwargs): slugs__slug=slug, slugs__content_type=ContentType.objects.get_for_model(Podcast), ) - podcast = podcast.prefetch_related('slugs', 'urls').get() + podcast = podcast.prefetch_related("slugs", "urls").get() except Podcast.DoesNotExist: raise Http404 @@ -396,7 +396,7 @@ def _decorator(request, podcast_id, *args, **kwargs): try: podcast = Podcast.objects.filter(id=podcast_id) - podcast = podcast.prefetch_related('slugs', 'urls').get() + podcast = podcast.prefetch_related("slugs", "urls").get() # if the podcast has a slug, redirect to its canonical URL if podcast.slug: diff --git a/mygpo/publisher/admin.py b/mygpo/publisher/admin.py index cc2f4b137..da6c8f3c0 100644 --- a/mygpo/publisher/admin.py +++ b/mygpo/publisher/admin.py @@ -8,11 +8,11 @@ class ClientAdmin(admin.ModelAdmin): """ Admin page for published podcasts""" # configuration for the list view - list_display = ('publisher', 'podcast') + list_display = ("publisher", "podcast") # fetch the related fields for the list_display - list_select_related = ('publisher', 'podcast') + list_select_related = ("publisher", "podcast") - raw_id_fields = ('publisher', 'podcast') + raw_id_fields = ("publisher", "podcast") show_full_result_count = False diff --git a/mygpo/publisher/auth.py b/mygpo/publisher/auth.py index c077e2eb0..af8a703bb 100644 --- a/mygpo/publisher/auth.py +++ b/mygpo/publisher/auth.py @@ -10,12 +10,12 @@ def require_publisher(protected_view): def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: - return HttpResponseRedirect('/login/') + return HttpResponseRedirect("/login/") if is_publisher(request.user): return protected_view(request, *args, **kwargs) - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") return wrapper diff --git a/mygpo/publisher/forms.py b/mygpo/publisher/forms.py index d2545de13..a85227d74 100644 --- a/mygpo/publisher/forms.py +++ b/mygpo/publisher/forms.py @@ -3,4 +3,4 @@ class SearchPodcastForm(forms.Form): - url = forms.URLField(label=_('URL')) + url = forms.URLField(label=_("URL")) diff --git a/mygpo/publisher/management/commands/make-publisher.py b/mygpo/publisher/management/commands/make-publisher.py index 2e7b1db4c..58fabb840 100644 --- a/mygpo/publisher/management/commands/make-publisher.py +++ b/mygpo/publisher/management/commands/make-publisher.py @@ -19,7 +19,7 @@ def handle(self, *args, **options): if len(args) < 2: print( - 'Usage: ./manage.py make-publisher [ ...]', + "Usage: ./manage.py make-publisher [ ...]", file=sys.stderr, ) return @@ -29,7 +29,7 @@ def handle(self, *args, **options): User = get_user_model() user = User.objects.get(username=username) if not user: - print('User %s does not exist' % username, file=sys.stderr) + print("User %s does not exist" % username, file=sys.stderr) return urls = args[1:] diff --git a/mygpo/publisher/migrations/0001_initial.py b/mygpo/publisher/migrations/0001_initial.py index 0be14a6bc..650df3562 100644 --- a/mygpo/publisher/migrations/0001_initial.py +++ b/mygpo/publisher/migrations/0001_initial.py @@ -9,28 +9,28 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('podcasts', '__first__'), + ("podcasts", "__first__"), ] operations = [ migrations.CreateModel( - name='PublishedPodcast', + name="PublishedPodcast", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ( - 'podcast', - models.ForeignKey(to='podcasts.Podcast', on_delete=models.CASCADE), + "podcast", + models.ForeignKey(to="podcasts.Podcast", on_delete=models.CASCADE), ), ( - 'publisher', + "publisher", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), diff --git a/mygpo/publisher/migrations/0002_auto_20140718_1457.py b/mygpo/publisher/migrations/0002_auto_20140718_1457.py index 7000355b3..ed766799c 100644 --- a/mygpo/publisher/migrations/0002_auto_20140718_1457.py +++ b/mygpo/publisher/migrations/0002_auto_20140718_1457.py @@ -6,10 +6,10 @@ class Migration(migrations.Migration): - dependencies = [('publisher', '0001_initial')] + dependencies = [("publisher", "0001_initial")] operations = [ migrations.AlterUniqueTogether( - name='publishedpodcast', unique_together=set([('publisher', 'podcast')]) + name="publishedpodcast", unique_together=set([("publisher", "podcast")]) ) ] diff --git a/mygpo/publisher/models.py b/mygpo/publisher/models.py index 7989615f2..868e5613c 100644 --- a/mygpo/publisher/models.py +++ b/mygpo/publisher/models.py @@ -23,11 +23,11 @@ def publish_podcasts(self, user, podcasts): if new: created += 1 - logger.info('Created publisher permissions for %r on %r', user, podcast) + logger.info("Created publisher permissions for %r on %r", user, podcast) else: existed += 1 logger.info( - 'Publisher permissions for %r on %r already exist', user, podcast + "Publisher permissions for %r on %r already exist", user, podcast ) return created, existed @@ -38,6 +38,6 @@ class PublishedPodcast(models.Model): podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE) class Meta: - unique_together = (('publisher', 'podcast'),) + unique_together = (("publisher", "podcast"),) objects = PublishedPodcastManager() diff --git a/mygpo/publisher/templatetags/pcharts.py b/mygpo/publisher/templatetags/pcharts.py index 2794be1f4..c509bed2d 100644 --- a/mygpo/publisher/templatetags/pcharts.py +++ b/mygpo/publisher/templatetags/pcharts.py @@ -7,7 +7,7 @@ @register.filter def bar_chart(parts): - maxv = max([int(x['y']) for x in parts]) + maxv = max([int(x["y"]) for x in parts]) bar_width = 15 bar_space = 15 group_space = 20 @@ -15,14 +15,14 @@ def bar_chart(parts): width = min(1000, ((bar_space + group_space) * (len(parts) + 1))) parts = [ - 'cht=bvg', # Vertical bar chart with grouped bars. - 'chs=%dx100' % width, - 'chl=%s' % '|'.join([x['x'] for x in parts]), - 'chd=t:%s' % ','.join([repr(int(x['y'])) for x in parts]), - 'chxt=x,y', # visible axes - 'chbh=%d,%d,%d' % (bar_width, bar_space, group_space), - 'chds=0,%d' % maxv, # avis scaling from 0 to max - 'chxr=1,0,%d' % maxv, # labeling for axis 1 (y) from 0 to max + "cht=bvg", # Vertical bar chart with grouped bars. + "chs=%dx100" % width, + "chl=%s" % "|".join([x["x"] for x in parts]), + "chd=t:%s" % ",".join([repr(int(x["y"])) for x in parts]), + "chxt=x,y", # visible axes + "chbh=%d,%d,%d" % (bar_width, bar_space, group_space), + "chds=0,%d" % maxv, # avis scaling from 0 to max + "chxr=1,0,%d" % maxv, # labeling for axis 1 (y) from 0 to max ] - return mark_safe('' % '&'.join(parts)) + return mark_safe('' % "&".join(parts)) diff --git a/mygpo/publisher/urls.py b/mygpo/publisher/urls.py index a9596957a..96deea12d 100644 --- a/mygpo/publisher/urls.py +++ b/mygpo/publisher/urls.py @@ -5,82 +5,82 @@ from mygpo.users import converters -register_converter(converters.UsernameConverter, 'username') +register_converter(converters.UsernameConverter, "username") urlpatterns = [ - path('', views.home, name='publisher'), + path("", views.home, name="publisher"), path( - '/update', + "/update", views.update_published_podcasts, - name='publisher-update', + name="publisher-update", ), path( - '/update-token', + "/update-token", views.new_update_token, - name='publisher-new-update-token', + name="publisher-new-update-token", ), path( - 'podcast//', views.podcast_slug, name='podcast-publisher-detail-slug' + "podcast//", + views.podcast_id, + name="podcast-publisher-detail-id", ), path( - 'podcast//update', - views.update_podcast_slug, - name='podcast-publisher-update-slug', + "podcast//update", + views.update_podcast_id, + name="podcast-publisher-update-id", ), path( - 'podcast//save', - views.save_podcast_slug, - name='podcast-publisher-save-slug', + "podcast//save", + views.save_podcast_id, + name="podcast-publisher-save-id", ), path( - 'podcast//episodes', - views.episodes_slug, - name='podcast-publisher-episodes-slug', + "podcast//episodes", + views.episodes_id, + name="podcast-publisher-episodes-id", ), path( - 'podcast//', - views.episode_slug, - name='episode-publisher-detail-slug', + "podcast//", + views.episode_id, + name="episode-publisher-detail-id", ), path( - 'podcast///set-slug', - views.update_episode_slug_slug, - name='publisher-set-episode-slug-slug', + "podcast///" "set-slug", + views.update_episode_slug_id, + name="publisher-set-episode-slug-id", ), path( - 'podcast//', - views.podcast_id, - name='podcast-publisher-detail-id', + "podcast//", views.podcast_slug, name="podcast-publisher-detail-slug" ), path( - 'podcast//update', - views.update_podcast_id, - name='podcast-publisher-update-id', + "podcast//update", + views.update_podcast_slug, + name="podcast-publisher-update-slug", ), path( - 'podcast//save', - views.save_podcast_id, - name='podcast-publisher-save-id', + "podcast//save", + views.save_podcast_slug, + name="podcast-publisher-save-slug", ), path( - 'podcast//episodes', - views.episodes_id, - name='podcast-publisher-episodes-id', + "podcast//episodes", + views.episodes_slug, + name="podcast-publisher-episodes-slug", ), path( - 'podcast//', - views.episode_id, - name='episode-publisher-detail-id', + "podcast//", + views.episode_slug, + name="episode-publisher-detail-slug", ), path( - 'podcast///' 'set-slug', - views.update_episode_slug_id, - name='publisher-set-episode-slug-id', + "podcast///set-slug", + views.update_episode_slug_slug, + name="publisher-set-episode-slug-slug", ), - path('group/', views.group_slug, name='group-publisher-slug'), - path('group/', views.group_id, name='group-publisher-id'), - path('podcast/search', views.search_podcast, name='podcast-publisher-search'), - path('link/', views.link, name='link-here'), - path('advertise', views.advertise, name='advertise'), + path("group/", views.group_slug, name="group-publisher-slug"), + path("group/", views.group_id, name="group-publisher-id"), + path("podcast/search", views.search_podcast, name="podcast-publisher-search"), + path("link/", views.link, name="link-here"), + path("advertise", views.advertise, name="advertise"), ] diff --git a/mygpo/publisher/utils.py b/mygpo/publisher/utils.py index c73e088c2..cfef27773 100644 --- a/mygpo/publisher/utils.py +++ b/mygpo/publisher/utils.py @@ -8,15 +8,15 @@ from mygpo.publisher.models import PublishedPodcast -ListenerData = namedtuple('ListenerData', 'date playcount episode') +ListenerData = namedtuple("ListenerData", "date playcount episode") def listener_data(podcasts, start_date=datetime(2010, 1, 1), leap=timedelta(days=1)): - """ Returns data for the podcast listener timeseries + """Returns data for the podcast listener timeseries An iterator with data for each day (starting from either the first released episode or the earliest play-event) is returned, where each day is - reresented by a ListenerData tuple. """ + reresented by a ListenerData tuple.""" # index episodes by releaes-date episodes = Episode.objects.filter(podcast__in=podcasts, released__gt=start_date) episodes = {e.released.date(): e for e in episodes} @@ -44,10 +44,10 @@ def listener_data(podcasts, start_date=datetime(2010, 1, 1), leap=timedelta(days def episode_listener_data( episode, start_date=datetime(2010, 1, 1), leap=timedelta(days=1) ): - """ Returns data for the episode listener timeseries + """Returns data for the episode listener timeseries An iterator with data for each day (starting from the first event - is returned, where each day is represented by a ListenerData tuple """ + is returned, where each day is represented by a ListenerData tuple""" history = EpisodeHistoryEntry.objects.filter( episode=episode, timestamp__gte=start_date ) @@ -79,7 +79,7 @@ def subscriber_data(podcasts): # TODO. rewrite for podcast in podcasts: - create_entry = lambda r: (r.timestamp.strftime('%y-%m'), r.subscriber_count) + create_entry = lambda r: (r.timestamp.strftime("%y-%m"), r.subscriber_count) subdata = [podcast.subscribers] @@ -90,7 +90,7 @@ def subscriber_data(podcasts): # create a list of {'x': label, 'y': value} coll_data = sorted( - [dict(x=a, y=b) for (a, b) in coll_data.items()], key=lambda x: x['x'] + [dict(x=a, y=b) for (a, b) in coll_data.items()], key=lambda x: x["x"] ) return coll_data diff --git a/mygpo/publisher/views.py b/mygpo/publisher/views.py index 3b4f88af4..e48c94004 100644 --- a/mygpo/publisher/views.py +++ b/mygpo/publisher/views.py @@ -53,24 +53,24 @@ def home(request): if is_publisher(request.user): podcasts = Podcast.objects.filter( publishedpodcast__publisher=request.user - ).prefetch_related('slugs') + ).prefetch_related("slugs") site = RequestSite(request) - update_token = request.user.profile.get_token('publisher_update_token') + update_token = request.user.profile.get_token("publisher_update_token") form = SearchPodcastForm() return render( request, - 'publisher/home.html', + "publisher/home.html", { - 'update_token': update_token, - 'podcasts': podcasts, - 'form': form, - 'site': site, + "update_token": update_token, + "podcasts": podcasts, + "form": form, + "site": site, }, ) else: site = RequestSite(request) - return render(request, 'publisher/info.html', {'site': site}) + return render(request, "publisher/info.html", {"site": site}) @vary_on_cookie @@ -79,11 +79,11 @@ def home(request): def search_podcast(request): form = SearchPodcastForm(request.POST) if form.is_valid(): - podcast_url = form.cleaned_data['url'] + podcast_url = form.cleaned_data["url"] podcast = get_object_or_404(Podcast, urls__url=podcast_url) - url = get_podcast_link_target(podcast, 'podcast-publisher-detail') + url = get_podcast_link_target(podcast, "podcast-publisher-detail") else: - url = reverse('publisher') + url = reverse("publisher") return HttpResponseRedirect(url) @@ -91,7 +91,7 @@ def search_podcast(request): @vary_on_cookie @cache_control(private=True) @require_publisher -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) def podcast(request, podcast): if not check_publisher_permission(request.user, podcast): @@ -100,7 +100,7 @@ def podcast(request, podcast): timeline_data = None # listener_data([podcast]) subscription_data = None # subscriber_data([podcast])[-20:] - update_token = request.user.profile.get_token('publisher_update_token') + update_token = request.user.profile.get_token("publisher_update_token") try: pubsubscription = HubSubscription.objects.get(topic_url=podcast.url) @@ -113,22 +113,22 @@ def podcast(request, podcast): update_results = update_results[:MAX_UPDATE_RESULTS] site = RequestSite(request) - feedurl_quoted = urllib.parse.quote(podcast.url.encode('ascii')) + feedurl_quoted = urllib.parse.quote(podcast.url.encode("ascii")) return render( request, - 'publisher/podcast.html', + "publisher/podcast.html", { - 'site': site, - 'podcast': podcast, - 'group': podcast.group, - 'form': None, - 'timeline_data': timeline_data, - 'subscriber_data': subscription_data, - 'update_token': update_token, - 'feedurl_quoted': feedurl_quoted, - 'pubsubscription': pubsubscription, - 'update_results': update_results, + "site": site, + "podcast": podcast, + "group": podcast.group, + "form": None, + "timeline_data": timeline_data, + "subscriber_data": subscription_data, + "update_token": update_token, + "feedurl_quoted": feedurl_quoted, + "pubsubscription": pubsubscription, + "update_results": update_results, }, ) @@ -149,11 +149,11 @@ def group(request, group): return render( request, - 'publisher/group.html', + "publisher/group.html", { - 'group': group, - 'timeline_data': timeline_data, - 'subscriber_data': subscription_data, + "group": group, + "timeline_data": timeline_data, + "subscriber_data": subscription_data, }, ) @@ -170,11 +170,11 @@ def update_podcast(request, podcast): messages.success( request, _( - 'The update has been scheduled. It might take some time until the results are visible.' + "The update has been scheduled. It might take some time until the results are visible." ), ) - url = get_podcast_link_target(podcast, 'podcast-publisher-detail') + url = get_podcast_link_target(podcast, "podcast-publisher-detail") return HttpResponseRedirect(url) @@ -182,33 +182,33 @@ def update_podcast(request, podcast): @cache_control(private=True) @require_publisher def save_podcast(request, podcast): - twitter = normalize_twitter(request.POST.get('twitter', '')) + twitter = normalize_twitter(request.POST.get("twitter", "")) podcast.twitter = twitter podcast.save() - messages.success(request, _('Data updated')) - url = get_podcast_link_target(podcast, 'podcast-publisher-detail') + messages.success(request, _("Data updated")) + url = get_podcast_link_target(podcast, "podcast-publisher-detail") return HttpResponseRedirect(url) @never_cache @require_publisher def new_update_token(request, username): - request.user.profile.create_new_token('publisher_update_token') + request.user.profile.create_new_token("publisher_update_token") request.user.profile.save() - messages.success(request, _('Publisher token updated')) - return HttpResponseRedirect(reverse('publisher')) + messages.success(request, _("Publisher token updated")) + return HttpResponseRedirect(reverse("publisher")) @never_cache -@requires_token(token_name='publisher_update_token') +@requires_token(token_name="publisher_update_token") def update_published_podcasts(request, username): User = get_user_model() user = get_object_or_404(User, username=username) published_podcasts = [pp.podcast for pp in user.publishedpodcast_set.all()] update_podcasts.delay([podcast.url for podcast in published_podcasts]) return HttpResponse( - 'Updated:\n' + '\n'.join([p.url for p in published_podcasts]), - content_type='text/plain', + "Updated:\n" + "\n".join([p.url for p in published_podcasts]), + content_type="text/plain", ) @@ -222,8 +222,8 @@ def episodes(request, podcast): episodes = ( Episode.objects.filter(podcast=podcast) - .select_related('podcast') - .prefetch_related('slugs', 'podcast__slugs') + .select_related("podcast") + .prefetch_related("slugs", "podcast__slugs") ) listeners = filter(None, (e.listeners for e in episodes)) @@ -231,15 +231,15 @@ def episodes(request, podcast): return render( request, - 'publisher/episodes.html', - {'podcast': podcast, 'episodes': episodes, 'max_listeners': max_listeners}, + "publisher/episodes.html", + {"podcast": podcast, "episodes": episodes, "max_listeners": max_listeners}, ) @require_publisher @vary_on_cookie @cache_control(private=True) -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) def episode(request, episode): site = RequestSite(request) @@ -248,37 +248,37 @@ def episode(request, episode): if not check_publisher_permission(request.user, podcast): return HttpResponseForbidden() - if request.method == 'POST': + if request.method == "POST": form = None # EpisodeForm(request.POST, instance=e) # if form.is_valid(): # form.save() - elif request.method == 'GET': + elif request.method == "GET": form = None # EpisodeForm(instance=e) timeline_data = list(episode_listener_data(episode)) return render( request, - 'publisher/episode.html', + "publisher/episode.html", { - 'is_secure': request.is_secure(), - 'domain': site.domain, - 'episode': episode, - 'podcast': podcast, - 'form': form, - 'timeline_data': timeline_data, + "is_secure": request.is_secure(), + "domain": site.domain, + "episode": episode, + "podcast": podcast, + "form": form, + "timeline_data": timeline_data, }, ) @require_publisher @never_cache -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def update_episode_slug(request, episode): """ sets a new "main" slug, and moves the existing to the merged slugs """ - new_slug = request.POST.get('slug') + new_slug = request.POST.get("slug") podcast = episode.podcast if new_slug: @@ -298,7 +298,7 @@ def update_episode_slug(request, episode): messages.warning( request, _( - 'Removed slug {slug} from {episode}'.format( + "Removed slug {slug} from {episode}".format( slug=new_slug, episode=other_episode.title ) ), @@ -310,7 +310,7 @@ def update_episode_slug(request, episode): cache.clear() return HttpResponseRedirect( - get_episode_link_target(episode, podcast, 'episode-publisher-detail') + get_episode_link_target(episode, podcast, "episode-publisher-detail") ) @@ -318,14 +318,14 @@ def update_episode_slug(request, episode): @cache_control(private=True) def link(request): current_site = RequestSite(request) - return render(request, 'link.html', {'url': current_site}) + return render(request, "link.html", {"url": current_site}) @vary_on_cookie @cache_control(private=True) def advertise(request): site = RequestSite(request) - return render(request, 'publisher/advertise.html', {'site': site}) + return render(request, "publisher/advertise.html", {"site": site}) def group_id_decorator(f): diff --git a/mygpo/pubsub/admin.py b/mygpo/pubsub/admin.py index 3f439bcf8..6fefd21fe 100644 --- a/mygpo/pubsub/admin.py +++ b/mygpo/pubsub/admin.py @@ -8,15 +8,15 @@ class HubSubscriptionAdmin(admin.ModelAdmin): """ Admin page for pubsubhubbub subscriptions """ # configuration for the list view - list_display = ('podcast', 'hub_url', 'mode', 'verified') + list_display = ("podcast", "hub_url", "mode", "verified") # fetch the related objects for the fields in list_display - list_select_related = ('podcast',) + list_select_related = ("podcast",) - raw_id_fields = ('podcast',) + raw_id_fields = ("podcast",) - list_filter = ('mode', 'verified') + list_filter = ("mode", "verified") - search_fields = ('topic_url', 'podcast__title', 'hub_url') + search_fields = ("topic_url", "podcast__title", "hub_url") show_full_result_count = False diff --git a/mygpo/pubsub/migrations/0001_initial.py b/mygpo/pubsub/migrations/0001_initial.py index f1b1e3e8d..ec525fdd5 100644 --- a/mygpo/pubsub/migrations/0001_initial.py +++ b/mygpo/pubsub/migrations/0001_initial.py @@ -7,40 +7,40 @@ class Migration(migrations.Migration): - dependencies = [('podcasts', '0028_episode_indexes')] + dependencies = [("podcasts", "0028_episode_indexes")] operations = [ migrations.CreateModel( - name='HubSubscription', + name="HubSubscription", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('topic_url', models.CharField(unique=True, max_length=2048)), - ('hub_url', models.CharField(max_length=1000)), - ('verify_token', models.CharField(max_length=32)), + ("topic_url", models.CharField(unique=True, max_length=2048)), + ("hub_url", models.CharField(max_length=1000)), + ("verify_token", models.CharField(max_length=32)), ( - 'mode', + "mode", models.CharField( blank=True, max_length=11, choices=[ - ('subscribe', 'subscribe'), - ('unsubscribe', 'unsubscribe'), + ("subscribe", "subscribe"), + ("unsubscribe", "unsubscribe"), ], ), ), - ('verified', models.BooleanField(default=False)), + ("verified", models.BooleanField(default=False)), ( - 'podcast', + "podcast", models.ForeignKey( - to='podcasts.Podcast', + to="podcasts.Podcast", on_delete=django.db.models.deletion.PROTECT, ), ), diff --git a/mygpo/pubsub/migrations/0002_created_modified.py b/mygpo/pubsub/migrations/0002_created_modified.py index 9ddb0afc8..4df2669a2 100644 --- a/mygpo/pubsub/migrations/0002_created_modified.py +++ b/mygpo/pubsub/migrations/0002_created_modified.py @@ -7,12 +7,12 @@ class Migration(migrations.Migration): - dependencies = [('pubsub', '0001_initial')] + dependencies = [("pubsub", "0001_initial")] operations = [ migrations.AddField( - model_name='hubsubscription', - name='created', + model_name="hubsubscription", + name="created", field=models.DateTimeField( default=datetime.datetime(2014, 8, 31, 12, 59, 26, 484_445), auto_now_add=True, @@ -20,8 +20,8 @@ class Migration(migrations.Migration): preserve_default=False, ), migrations.AddField( - model_name='hubsubscription', - name='modified', + model_name="hubsubscription", + name="modified", field=models.DateTimeField( default=datetime.datetime(2014, 8, 31, 12, 59, 36, 369_407), auto_now=True, diff --git a/mygpo/pubsub/models.py b/mygpo/pubsub/models.py index 0389d8205..ecb688771 100644 --- a/mygpo/pubsub/models.py +++ b/mygpo/pubsub/models.py @@ -9,14 +9,14 @@ class SubscriptionError(Exception): class HubSubscription(UpdateInfoModel): - """ A client-side PubSubHubbub subscription + """A client-side PubSubHubbub subscription - https://code.google.com/p/pubsubhubbub/ """ + https://code.google.com/p/pubsubhubbub/""" - SUBSCRIBE = 'subscribe' - UNSUBSCRIBE = 'unsubscribe' + SUBSCRIBE = "subscribe" + UNSUBSCRIBE = "unsubscribe" - MODE_CHOICES = ((SUBSCRIBE, 'subscribe'), (UNSUBSCRIBE, 'unsubscribe')) + MODE_CHOICES = ((SUBSCRIBE, "subscribe"), (UNSUBSCRIBE, "unsubscribe")) # podcast to which the subscription belongs podcast = models.ForeignKey(Podcast, on_delete=models.PROTECT) diff --git a/mygpo/pubsub/urls.py b/mygpo/pubsub/urls.py index e5ee4a1c0..1a6c838c7 100644 --- a/mygpo/pubsub/urls.py +++ b/mygpo/pubsub/urls.py @@ -4,5 +4,5 @@ urlpatterns = [ - path('subscribe', views.SubscribeView.as_view(), name='pubsub-subscribe') + path("subscribe", views.SubscribeView.as_view(), name="pubsub-subscribe") ] diff --git a/mygpo/pubsub/utils.py b/mygpo/pubsub/utils.py index 5855883ac..8b520f4bf 100644 --- a/mygpo/pubsub/utils.py +++ b/mygpo/pubsub/utils.py @@ -16,31 +16,31 @@ logger = logging.getLogger(__name__) -def subscribe(podcast, feedurl, huburl, base_url, mode='subscribe'): +def subscribe(podcast, feedurl, huburl, base_url, mode="subscribe"): """ Subscribe to the feed at a Hub """ - logger.info('subscribing for {feed} at {hub}'.format(feed=feedurl, hub=huburl)) - verify = 'sync' + logger.info("subscribing for {feed} at {hub}".format(feed=feedurl, hub=huburl)) + verify = "sync" - token_max_len = HubSubscription._meta.get_field('verify_token').max_length + token_max_len = HubSubscription._meta.get_field("verify_token").max_length subscription, created = HubSubscription.objects.get_or_create( topic_url=feedurl, defaults={ - 'verify_token': random_token(token_max_len), - 'mode': '', - 'podcast': podcast, + "verify_token": random_token(token_max_len), + "mode": "", + "podcast": podcast, }, ) if subscription.mode == mode: if subscription.verified: - logger.info('subscription already exists') + logger.info("subscription already exists") return else: logger.info( - 'subscription exists but has wrong mode: ' - + 'old: %(oldmode)s, new: %(newmode)s. Overwriting.' + "subscription exists but has wrong mode: " + + "old: %(oldmode)s, new: %(newmode)s. Overwriting." % dict(oldmode=subscription.mode, newmode=mode) ) @@ -57,7 +57,7 @@ def subscribe(podcast, feedurl, huburl, base_url, mode='subscribe'): } data = urllib.parse.urlencode(list(data.items())) - logger.debug('sending request: %s' % repr(data)) + logger.debug("sending request: %s" % repr(data)) resp = None @@ -66,7 +66,7 @@ def subscribe(podcast, feedurl, huburl, base_url, mode='subscribe'): except urllib.error.HTTPError as e: if e.code != 204: # we actually expect a 204 return code - msg = 'Could not send subscription to Hub: HTTP Error %d: %s' % ( + msg = "Could not send subscription to Hub: HTTP Error %d: %s" % ( e.code, e.reason, ) @@ -74,20 +74,20 @@ def subscribe(podcast, feedurl, huburl, base_url, mode='subscribe'): raise SubscriptionError(msg) except Exception as e: - msg = 'Could not send subscription to Hub: %s' % repr(e) + msg = "Could not send subscription to Hub: %s" % repr(e) logger.warning(msg) raise SubscriptionError(msg) if resp: status = resp.code if status != 204: - logger.warning('received incorrect status %d' % status) - raise SubscriptionError('Subscription has not been accepted by ' 'the Hub') + logger.warning("received incorrect status %d" % status) + raise SubscriptionError("Subscription has not been accepted by " "the Hub") def callback_url(feedurl, base_url): - callback = reverse('pubsub-subscribe') - param = urllib.parse.urlencode([('url', feedurl)]) - return '{base}{callback}?{param}'.format( + callback = reverse("pubsub-subscribe") + param = urllib.parse.urlencode([("url", feedurl)]) + return "{base}{callback}?{param}".format( base=base_url, callback=callback, param=param ) diff --git a/mygpo/pubsub/views.py b/mygpo/pubsub/views.py index 06fbed306..0f8fee1f9 100644 --- a/mygpo/pubsub/views.py +++ b/mygpo/pubsub/views.py @@ -28,17 +28,17 @@ def get(self, request): # received arguments: hub.mode, hub.topic, hub.challenge, # hub.lease_seconds, hub.verify_token - mode = request.GET.get('hub.mode') - feed_url = request.GET.get('hub.topic') - challenge = request.GET.get('hub.challenge') - lease_seconds = request.GET.get('hub.lease_seconds') - verify_token = request.GET.get('hub.verify_token') + mode = request.GET.get("hub.mode") + feed_url = request.GET.get("hub.topic") + challenge = request.GET.get("hub.challenge") + lease_seconds = request.GET.get("hub.lease_seconds") + verify_token = request.GET.get("hub.verify_token") logger.debug( ( - 'received subscription-parameters: mode: %(mode)s, ' - + 'topic: %(topic)s, challenge: %(challenge)s, lease_seconds: ' - + '%(lease_seconds)s, verify_token: %(verify_token)s' + "received subscription-parameters: mode: %(mode)s, " + + "topic: %(topic)s, challenge: %(challenge)s, lease_seconds: " + + "%(lease_seconds)s, verify_token: %(verify_token)s" ) % dict( mode=mode, @@ -53,49 +53,49 @@ def get(self, request): subscription = HubSubscription.objects.get(topic_url=feed_url) except HubSubscription.DoesNotExist: - logger.warning('subscription does not exist') + logger.warning("subscription does not exist") return HttpResponseNotFound() if subscription.mode != mode: - logger.warning('invalid mode, %s expected' % subscription.mode) + logger.warning("invalid mode, %s expected" % subscription.mode) return HttpResponseNotFound() if subscription.verify_token != verify_token: logger.warning( - 'invalid verify_token, %s expected' % subscription.verify_token + "invalid verify_token, %s expected" % subscription.verify_token ) return HttpResponseNotFound() subscription.verified = True subscription.save() - logger.info('subscription confirmed') + logger.info("subscription confirmed") return HttpResponse(challenge) def post(self, request): """ Callback to notify about a feed update """ - feed_url = request.GET.get('url') + feed_url = request.GET.get("url") if not feed_url: - logger.info('received notification without url') + logger.info("received notification without url") return HttpResponse(status=400) - logger.info('received notification for %s' % feed_url) + logger.info("received notification for %s" % feed_url) try: subscription = HubSubscription.objects.get(topic_url=feed_url) except HubSubscription.DoesNotExist: - logger.warning('no subscription for this URL') + logger.warning("no subscription for this URL") return HttpResponse(status=400) - if subscription.mode != 'subscribe': - logger.warning('invalid subscription mode: %s' % subscription.mode) + if subscription.mode != "subscribe": + logger.warning("invalid subscription mode: %s" % subscription.mode) return HttpResponse(status=400) if not subscription.verified: - logger.warning('the subscription has not yet been verified') + logger.warning("the subscription has not yet been verified") return HttpResponse(status=400) subscription_updated.send(sender=feed_url) diff --git a/mygpo/search/__init__.py b/mygpo/search/__init__.py index dc8a6a995..4e04b3e28 100644 --- a/mygpo/search/__init__.py +++ b/mygpo/search/__init__.py @@ -1,8 +1,8 @@ -default_app_config = 'mygpo.search.apps.SearchConfig' +default_app_config = "mygpo.search.apps.SearchConfig" # Field that should be indexed, with their levels per # https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/search/#weighting-queries -INDEX_FIELDS = {'title': 'A', 'description': 'B'} +INDEX_FIELDS = {"title": "A", "description": "B"} def get_index_fields(podcast): diff --git a/mygpo/search/apps.py b/mygpo/search/apps.py index f551dbda9..2a67b2bc5 100644 --- a/mygpo/search/apps.py +++ b/mygpo/search/apps.py @@ -2,5 +2,5 @@ class SearchConfig(AppConfig): - name = 'mygpo.search' - verbose_name = 'Search' + name = "mygpo.search" + verbose_name = "Search" diff --git a/mygpo/search/index.py b/mygpo/search/index.py index 2ed820859..9894349c3 100644 --- a/mygpo/search/index.py +++ b/mygpo/search/index.py @@ -32,14 +32,14 @@ def search_podcasts(query): query = SearchQuery(query) results = ( - Podcast.objects.annotate(rank=SearchRank(F('search_vector'), query)) + Podcast.objects.annotate(rank=SearchRank(F("search_vector"), query)) .annotate( order=ExpressionWrapper( - F('rank') * F('subscribers'), output_field=FloatField() + F("rank") * F("subscribers"), output_field=FloatField() ) ) .filter(rank__gte=SEARCH_CUTOFF) - .order_by('-order')[:100] + .order_by("-order")[:100] ) logger.debug( diff --git a/mygpo/search/json.py b/mygpo/search/json.py index 69dfa558f..7af03b2f8 100644 --- a/mygpo/search/json.py +++ b/mygpo/search/json.py @@ -4,32 +4,32 @@ def podcast_to_json(podcast): """ Convert a podcast to JSON for indexing """ doc = { - 'title': podcast.title, - 'subtitle': podcast.subtitle, - 'description': podcast.description, - 'link': podcast.link, - 'language': podcast.language, - 'last_update': podcast.last_update, - 'created': podcast.created, + "title": podcast.title, + "subtitle": podcast.subtitle, + "description": podcast.description, + "link": podcast.link, + "language": podcast.language, + "last_update": podcast.last_update, + "created": podcast.created, # modified is not indexed - 'license': podcast.license, # maybe get a license name here? + "license": podcast.license, # maybe get a license name here? # flattr_url - 'content_types': list(filter(None, podcast.content_types)), - 'outdated': podcast.outdated, - 'author': podcast.author, - 'logo_url': podcast.logo_url, + "content_types": list(filter(None, podcast.content_types)), + "outdated": podcast.outdated, + "author": podcast.author, + "logo_url": podcast.logo_url, # group, group_member_name ??? - 'subscribers': podcast.subscribers, + "subscribers": podcast.subscribers, # restrictions ? # common_episode_title # new_location - 'latest_episode_timestamp': podcast.latest_episode_timestamp, - 'episode_count': podcast.episode_count, + "latest_episode_timestamp": podcast.latest_episode_timestamp, + "episode_count": podcast.episode_count, # hub - 'twitter': podcast.twitter, + "twitter": podcast.twitter, # update_interval - 'slugs': [s.slug for s in podcast.slugs.all()], - 'urls': [u.url for u in podcast.urls.all()], + "slugs": [s.slug for s in podcast.slugs.all()], + "urls": [u.url for u in podcast.urls.all()], } return doc diff --git a/mygpo/search/tasks.py b/mygpo/search/tasks.py index 4620aa759..7fb635730 100644 --- a/mygpo/search/tasks.py +++ b/mygpo/search/tasks.py @@ -29,18 +29,18 @@ def update_search_index(run_every=UPDATE_INTERVAL): """ Schedules podcast updates that are due within ``interval`` """ - logger.info('Updating search index') + logger.info("Updating search index") # We avoid an UPDATE, because it cannot be LIMITed, the thus might # be to expensive in a single statement. # We could use select_for_update(), but there is no need for consistency # between multiple podcasts. - to_update = Podcast.objects.filter(search_index_uptodate=False).only('pk')[ + to_update = Podcast.objects.filter(search_index_uptodate=False).only("pk")[ :MAX_INDEX ] count = to_update.count() - logger.info('Updating search index for {} podcasts'.format(count)) + logger.info("Updating search index for {} podcasts".format(count)) vectors = _get_search_vectors() @@ -49,7 +49,7 @@ def update_search_index(run_every=UPDATE_INTERVAL): search_vector=vectors, search_index_uptodate=True ) - logger.info('Finished indexing podcasts') + logger.info("Finished indexing podcasts") def _get_search_vectors(): diff --git a/mygpo/search/tests.py b/mygpo/search/tests.py index 4dc467b1e..bb05d5b73 100644 --- a/mygpo/search/tests.py +++ b/mygpo/search/tests.py @@ -18,8 +18,8 @@ def test_search_podcast(self): # create a podcast podcast = Podcast( id=uuid.uuid1(), - title='Awesome Podcast', - description='An amazing podcast on many topics', + title="Awesome Podcast", + description="An amazing podcast on many topics", ) podcast.save() @@ -27,30 +27,30 @@ def test_search_podcast(self): update_search_index() # search for the podcast - results = search_podcasts('awesome') + results = search_podcasts("awesome") self.assertEqual(results[0].id, podcast.id) @override_settings(QUERY_LENGTH_CUTOFF=3) def test_shortest_search_podcast(self): """ - Search for a podcast with query length smaller than 3 - With QUERY_LENGTH_CUTOFF = 3 - Server would normally time out, however Podcasts exist for the given - search term. + Search for a podcast with query length smaller than 3 + With QUERY_LENGTH_CUTOFF = 3 + Server would normally time out, however Podcasts exist for the given + search term. """ # create a podcast podcast = Podcast( id=uuid.uuid1(), - title='The Tricky Podcast', - description='The only podcast containing tricky messages.', + title="The Tricky Podcast", + description="The only podcast containing tricky messages.", ) podcast.save() # explicitly trigger a search index update update_search_index() - results = search_podcasts('The') + results = search_podcasts("The") self.assertEqual(len(results), 0) - results = search_podcasts('The Tricky') + results = search_podcasts("The Tricky") self.assertEqual(results[0].id, podcast.id) diff --git a/mygpo/settings.py b/mygpo/settings.py index 648f8b5dc..778807192 100644 --- a/mygpo/settings.py +++ b/mygpo/settings.py @@ -21,48 +21,47 @@ def get_bool(name, default): - return os.getenv(name, str(default)).lower() == 'true' + return os.getenv(name, str(default)).lower() == "true" def get_intOrNone(name, default): """ Parses the env variable, accepts ints and literal None""" value = os.getenv(name, str(default)) - if value.lower() == 'none': + if value.lower() == "none": return None return int(value) -DEBUG = get_bool('DEBUG', False) +DEBUG = get_bool("DEBUG", False) - -ADMINS = re.findall(r'\s*([^<]+) <([^>]+)>\s*', os.getenv('ADMINS', '')) +ADMINS = re.findall(r"\s*([^<]+) <([^>]+)>\s*", os.getenv("ADMINS", "")) MANAGERS = ADMINS DATABASES = { - 'default': dj_database_url.config(default='postgres://mygpo:mygpo@localhost/mygpo') + "default": dj_database_url.config(default="postgres://mygpo:mygpo@localhost/mygpo") } -_USE_GEVENT = get_bool('USE_GEVENT', False) +_USE_GEVENT = get_bool("USE_GEVENT", False) if _USE_GEVENT: # see https://github.com/jneight/django-db-geventpool - default = DATABASES['default'] - default['ENGINE'] = ('django_db_geventpool.backends.postgresql_psycopg2',) - default['CONN_MAX_AGE'] = 0 - options = default.get('OPTIONS', {}) - options['MAX_CONNS'] = 20 + default = DATABASES["default"] + default["ENGINE"] = ("django_db_geventpool.backends.postgresql_psycopg2",) + default["CONN_MAX_AGE"] = 0 + options = default.get("OPTIONS", {}) + options["MAX_CONNS"] = 20 -_cache_used = bool(os.getenv('CACHE_BACKEND', False)) +_cache_used = bool(os.getenv("CACHE_BACKEND", False)) if _cache_used: CACHES = {} - CACHES['default'] = { - 'BACKEND': os.getenv( - 'CACHE_BACKEND', 'django.core.cache.backends.memcached.MemcachedCache' + CACHES["default"] = { + "BACKEND": os.getenv( + "CACHE_BACKEND", "django.core.cache.backends.memcached.MemcachedCache" ), - 'LOCATION': os.getenv('CACHE_LOCATION'), + "LOCATION": os.getenv("CACHE_LOCATION"), } @@ -71,11 +70,11 @@ def get_intOrNone(name, default): # although not all choices may be available on all operating systems. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" SITE_ID = 1 @@ -86,50 +85,49 @@ def get_intOrNone(name, default): # Static Files -STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') -STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, "..", "static") +STATIC_URL = "/static/" -STATICFILES_DIRS = (os.path.abspath(os.path.join(BASE_DIR, '..', 'static')),) # Media Files MEDIA_ROOT = os.getenv( - 'MEDIA_ROOT', os.path.abspath(os.path.join(BASE_DIR, '..', 'media')) + "MEDIA_ROOT", os.path.abspath(os.path.join(BASE_DIR, "..", "media")) ) -MEDIA_URL = '/media/' +MEDIA_URL = "/media/" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'OPTIONS': { - 'debug': DEBUG, - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'mygpo.web.google.analytics', - 'mygpo.web.google.adsense', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "OPTIONS": { + "debug": DEBUG, + "context_processors": [ + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "mygpo.web.google.analytics", + "mygpo.web.google.adsense", # make the debug variable available in templates # https://docs.djangoproject.com/en/dev/ref/templates/api/#django-core-context-processors-debug - 'django.template.context_processors.debug', + "django.template.context_processors.debug", # required so that the request obj can be accessed from # templates. this is used to direct users to previous # page after login - 'django.template.context_processors.request', + "django.template.context_processors.request", ], - 'libraries': {'staticfiles': 'django.templatetags.static'}, - 'loaders': [ + "libraries": {"staticfiles": "django.templatetags.static"}, + "loaders": [ ( - 'django.template.loaders.cached.Loader', - ['django.template.loaders.app_directories.Loader'], + "django.template.loaders.cached.Loader", + ["django.template.loaders.app_directories.Loader"], ) ], }, @@ -138,53 +136,53 @@ def get_intOrNone(name, default): MIDDLEWARE = [ - 'whitenoise.middleware.WhiteNoiseMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", ] -ROOT_URLCONF = 'mygpo.urls' +ROOT_URLCONF = "mygpo.urls" INSTALLED_APPS = [ - 'django.contrib.contenttypes', - 'django.contrib.messages', - 'django.contrib.admin', - 'django.contrib.humanize', - 'django.contrib.auth', - 'django.contrib.sessions', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django.contrib.postgres', - 'django_celery_results', - 'django_celery_beat', - 'mygpo.core', - 'mygpo.podcasts', - 'mygpo.chapters', - 'mygpo.search', - 'mygpo.users', - 'mygpo.api', - 'mygpo.web', - 'mygpo.publisher', - 'mygpo.subscriptions', - 'mygpo.history', - 'mygpo.favorites', - 'mygpo.usersettings', - 'mygpo.data', - 'mygpo.userfeeds', - 'mygpo.suggestions', - 'mygpo.directory', - 'mygpo.categories', - 'mygpo.episodestates', - 'mygpo.maintenance', - 'mygpo.share', - 'mygpo.administration', - 'mygpo.pubsub', - 'mygpo.podcastlists', - 'mygpo.votes', + "django.contrib.contenttypes", + "django.contrib.messages", + "django.contrib.admin", + "django.contrib.humanize", + "django.contrib.auth", + "django.contrib.sessions", + "django.contrib.staticfiles", + "django.contrib.sites", + "django.contrib.postgres", + "django_celery_results", + "django_celery_beat", + "mygpo.core", + "mygpo.podcasts", + "mygpo.chapters", + "mygpo.search", + "mygpo.users", + "mygpo.api", + "mygpo.web", + "mygpo.publisher", + "mygpo.subscriptions", + "mygpo.history", + "mygpo.favorites", + "mygpo.usersettings", + "mygpo.data", + "mygpo.userfeeds", + "mygpo.suggestions", + "mygpo.directory", + "mygpo.categories", + "mygpo.episodestates", + "mygpo.maintenance", + "mygpo.share", + "mygpo.administration", + "mygpo.pubsub", + "mygpo.podcastlists", + "mygpo.votes", ] @@ -192,8 +190,8 @@ def get_intOrNone(name, default): if DEBUG: import debug_toolbar - INSTALLED_APPS += ['debug_toolbar'] - MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] + INSTALLED_APPS += ["debug_toolbar"] + MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] except ImportError: pass @@ -203,18 +201,18 @@ def get_intOrNone(name, default): if DEBUG: import django_extensions - INSTALLED_APPS += ['django_extensions'] + INSTALLED_APPS += ["django_extensions"] except ImportError: pass -ACCOUNT_ACTIVATION_DAYS = int(os.getenv('ACCOUNT_ACTIVATION_DAYS', 7)) +ACCOUNT_ACTIVATION_DAYS = int(os.getenv("ACCOUNT_ACTIVATION_DAYS", 7)) AUTHENTICATION_BACKENDS = ( - 'mygpo.users.backend.CaseInsensitiveModelBackend', - 'mygpo.web.auth.EmailAuthenticationBackend', + "mygpo.users.backend.CaseInsensitiveModelBackend", + "mygpo.web.auth.EmailAuthenticationBackend", ) SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" @@ -222,184 +220,182 @@ def get_intOrNone(name, default): # TODO: use (default) JSON serializer for security # this would currently fail as we're (de)serializing datetime objects # https://docs.djangoproject.com/en/1.5/topics/http/sessions/#session-serialization -SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' - - -MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' +SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer" -USER_CLASS = 'mygpo.users.models.User' -LOGIN_URL = '/login/' +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" -CSRF_FAILURE_VIEW = 'mygpo.web.views.csrf_failure' +USER_CLASS = "mygpo.users.models.User" -DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', '') +LOGIN_URL = "/login/" -SERVER_EMAIL = os.getenv('SERVER_EMAIL', DEFAULT_FROM_EMAIL) +CSRF_FAILURE_VIEW = "mygpo.web.views.csrf_failure" -SECRET_KEY = os.getenv('SECRET_KEY', '') -if 'pytest' in sys.argv[0]: - SECRET_KEY = 'test' +DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "") -GOOGLE_ANALYTICS_PROPERTY_ID = os.getenv('GOOGLE_ANALYTICS_PROPERTY_ID', '') +SERVER_EMAIL = os.getenv("SERVER_EMAIL", DEFAULT_FROM_EMAIL) +SECRET_KEY = os.getenv("SECRET_KEY", "") -DIRECTORY_EXCLUDED_TAGS = os.getenv('DIRECTORY_EXCLUDED_TAGS', '').split() +if "pytest" in sys.argv[0]: + SECRET_KEY = "test" +GOOGLE_ANALYTICS_PROPERTY_ID = os.getenv("GOOGLE_ANALYTICS_PROPERTY_ID", "") -FLICKR_API_KEY = os.getenv('FLICKR_API_KEY', '') +DIRECTORY_EXCLUDED_TAGS = os.getenv("DIRECTORY_EXCLUDED_TAGS", "").split() -SOUNDCLOUD_CONSUMER_KEY = os.getenv('SOUNDCLOUD_CONSUMER_KEY', '') +FLICKR_API_KEY = os.getenv("FLICKR_API_KEY", "") +SOUNDCLOUD_CONSUMER_KEY = os.getenv("SOUNDCLOUD_CONSUMER_KEY", "") -MAINTENANCE = get_bool('MAINTENANCE', False) +MAINTENANCE = get_bool("MAINTENANCE", False) -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': {'format': '%(asctime)s %(name)s %(levelname)s %(message)s'} + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": {"format": "%(asctime)s %(name)s %(levelname)s %(message)s"} }, - 'filters': {'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}}, - 'handlers': { - 'console': { - 'level': os.getenv('LOGGING_CONSOLE_LEVEL', 'DEBUG'), - 'class': 'logging.StreamHandler', - 'formatter': 'verbose', + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "handlers": { + "console": { + "level": os.getenv("LOGGING_CONSOLE_LEVEL", "DEBUG"), + "class": "logging.StreamHandler", + "formatter": "verbose", }, - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler', + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", }, }, - 'loggers': { - 'django': { - 'handlers': os.getenv('LOGGING_DJANGO_HANDLERS', 'console').split(), - 'propagate': True, - 'level': os.getenv('LOGGING_DJANGO_LEVEL', 'WARN'), + "loggers": { + "django": { + "handlers": os.getenv("LOGGING_DJANGO_HANDLERS", "console").split(), + "propagate": True, + "level": os.getenv("LOGGING_DJANGO_LEVEL", "WARN"), }, - 'mygpo': { - 'handlers': os.getenv('LOGGING_MYGPO_HANDLERS', 'console').split(), - 'level': os.getenv('LOGGING_MYGPO_LEVEL', 'INFO'), + "mygpo": { + "handlers": os.getenv("LOGGING_MYGPO_HANDLERS", "console").split(), + "level": os.getenv("LOGGING_MYGPO_LEVEL", "INFO"), }, - 'celery': { - 'handlers': os.getenv('LOGGING_CELERY_HANDLERS', 'console').split(), - 'level': os.getenv('LOGGING_CELERY_LEVEL', 'DEBUG'), + "celery": { + "handlers": os.getenv("LOGGING_CELERY_HANDLERS", "console").split(), + "level": os.getenv("LOGGING_CELERY_LEVEL", "DEBUG"), }, }, } -_use_log_file = bool(os.getenv('LOGGING_FILENAME', False)) +_use_log_file = bool(os.getenv("LOGGING_FILENAME", False)) if _use_log_file: - LOGGING['handlers']['file'] = { - 'level': 'INFO', - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.getenv('LOGGING_FILENAME'), - 'maxBytes': 10_000_000, - 'backupCount': 10, - 'formatter': 'verbose', + LOGGING["handlers"]["file"] = { + "level": "INFO", + "class": "logging.handlers.RotatingFileHandler", + "filename": os.getenv("LOGGING_FILENAME"), + "maxBytes": 10_000_000, + "backupCount": 10, + "formatter": "verbose", } -DATA_UPLOAD_MAX_MEMORY_SIZE = get_intOrNone('DATA_UPLOAD_MAX_MEMORY_SIZE', None) +DATA_UPLOAD_MAX_MEMORY_SIZE = get_intOrNone("DATA_UPLOAD_MAX_MEMORY_SIZE", None) # minimum number of subscribers a podcast must have to be assigned a slug -PODCAST_SLUG_SUBSCRIBER_LIMIT = int(os.getenv('PODCAST_SLUG_SUBSCRIBER_LIMIT', 10)) +PODCAST_SLUG_SUBSCRIBER_LIMIT = int(os.getenv("PODCAST_SLUG_SUBSCRIBER_LIMIT", 10)) # minimum number of subscribers that a podcast needs to "push" one of its # categories to the top -MIN_SUBSCRIBERS_CATEGORY = int(os.getenv('MIN_SUBSCRIBERS_CATEGORY', 10)) +MIN_SUBSCRIBERS_CATEGORY = int(os.getenv("MIN_SUBSCRIBERS_CATEGORY", 10)) # maximum number of episode actions that the API processes immediatelly before # returning the response. Larger requests will be handled in background. # Handler can be set to None to disable -API_ACTIONS_MAX_NONBG = get_intOrNone('API_ACTIONS_MAX_NONBG', 100) -API_ACTIONS_BG_HANDLER = 'mygpo.api.tasks.episode_actions_celery_handler' +API_ACTIONS_MAX_NONBG = get_intOrNone("API_ACTIONS_MAX_NONBG", 100) +API_ACTIONS_BG_HANDLER = "mygpo.api.tasks.episode_actions_celery_handler" -ADSENSE_CLIENT = os.getenv('ADSENSE_CLIENT', '') +ADSENSE_CLIENT = os.getenv("ADSENSE_CLIENT", "") -ADSENSE_SLOT_BOTTOM = os.getenv('ADSENSE_SLOT_BOTTOM', '') +ADSENSE_SLOT_BOTTOM = os.getenv("ADSENSE_SLOT_BOTTOM", "") # we're running behind a proxy that sets the X-Forwarded-Proto header correctly # see https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # enabled access to staff-only areas with ?staff= -STAFF_TOKEN = os.getenv('STAFF_TOKEN', None) +STAFF_TOKEN = os.getenv("STAFF_TOKEN", None) # The User-Agent string used for outgoing HTTP requests -USER_AGENT = 'gpodder.net (+https://github.com/gpodder/mygpo)' +USER_AGENT = "gpodder.net (+https://github.com/gpodder/mygpo)" # Base URL of the website that is used if the actually used parameters is not # available. Request handlers, for example, can access the requested domain. # Code that runs in background can not do this, and therefore requires a # default value. This should be set to something like 'http://example.com' -DEFAULT_BASE_URL = os.getenv('DEFAULT_BASE_URL', '') +DEFAULT_BASE_URL = os.getenv("DEFAULT_BASE_URL", "") ### Celery -CELERY_BROKER_URL = os.getenv('BROKER_URL', 'redis://localhost') -CELERY_RESULT_BACKEND = 'django-db' +CELERY_BROKER_URL = os.getenv("BROKER_URL", "redis://localhost") +CELERY_RESULT_BACKEND = "django-db" CELERY_RESULT_EXPIRES = 60 * 60 # 1h expiry time in seconds -CELERY_ACCEPT_CONTENT = ['json'] +CELERY_ACCEPT_CONTENT = ["json"] ### Google API -GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID', '') -GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET', '') +GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID", "") +GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET", "") # URL where users of the site can get support -SUPPORT_URL = os.getenv('SUPPORT_URL', '') +SUPPORT_URL = os.getenv("SUPPORT_URL", "") -FEEDSERVICE_URL = os.getenv('FEEDSERVICE_URL', 'http://feeds.gpodder.net/') +FEEDSERVICE_URL = os.getenv("FEEDSERVICE_URL", "http://feeds.gpodder.net/") # time for how long an activation is valid; after that, an unactivated user # will be deleted -ACTIVATION_VALID_DAYS = int(os.getenv('ACTIVATION_VALID_DAYS', 10)) +ACTIVATION_VALID_DAYS = int(os.getenv("ACTIVATION_VALID_DAYS", 10)) OPBEAT = { - "ORGANIZATION_ID": os.getenv('OPBEAT_ORGANIZATION_ID', ''), - "APP_ID": os.getenv('OPBEAT_APP_ID', ''), - "SECRET_TOKEN": os.getenv('OPBEAT_SECRET_TOKEN', ''), + "ORGANIZATION_ID": os.getenv("OPBEAT_ORGANIZATION_ID", ""), + "APP_ID": os.getenv("OPBEAT_APP_ID", ""), + "SECRET_TOKEN": os.getenv("OPBEAT_SECRET_TOKEN", ""), } -LOCALE_PATHS = [os.path.abspath(os.path.join(BASE_DIR, 'locale'))] +LOCALE_PATHS = [os.path.abspath(os.path.join(BASE_DIR, "locale"))] -INTERNAL_IPS = os.getenv('INTERNAL_IPS', '').split() +INTERNAL_IPS = os.getenv("INTERNAL_IPS", "").split() EMAIL_BACKEND = os.getenv( - 'EMAIL_BACKEND', 'django.core.mail.backends.smtp.EmailBackend' + "EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend" ) -PODCAST_AD_ID = os.getenv('PODCAST_AD_ID') +PODCAST_AD_ID = os.getenv("PODCAST_AD_ID") -MAX_EPISODE_ACTIONS = int(os.getenv('MAX_EPISODE_ACTIONS', 1000)) +MAX_EPISODE_ACTIONS = int(os.getenv("MAX_EPISODE_ACTIONS", 1000)) -SEARCH_CUTOFF = float(os.getenv('SEARCH_CUTOFF', 0.3)) +SEARCH_CUTOFF = float(os.getenv("SEARCH_CUTOFF", 0.3)) # Maximum non-whitespace length of search query # If length of query is shorter than QUERY_LENGTH_CUTOFF, no results # will be returned to avoid a server timeout due to too many possible # responses -QUERY_LENGTH_CUTOFF = int(os.getenv('QUERY_LENGTH_CUTOFF', 3)) +QUERY_LENGTH_CUTOFF = int(os.getenv("QUERY_LENGTH_CUTOFF", 3)) ### Sentry @@ -410,9 +406,9 @@ def get_intOrNone(name, default): from sentry_sdk.integrations.redis import RedisIntegration # Sentry Data Source Name (DSN) - sentry_dsn = os.getenv('SENTRY_DSN', '') + sentry_dsn = os.getenv("SENTRY_DSN", "") if not sentry_dsn: - raise ValueError('Could not set up sentry because ' 'SENTRY_DSN is not set') + raise ValueError("Could not set up sentry because " "SENTRY_DSN is not set") sentry_sdk.init( dsn=sentry_dsn, diff --git a/mygpo/share/templatetags/gravatar.py b/mygpo/share/templatetags/gravatar.py index eca0278be..5ee520e85 100644 --- a/mygpo/share/templatetags/gravatar.py +++ b/mygpo/share/templatetags/gravatar.py @@ -8,7 +8,7 @@ register = template.Library() -GRAVATAR_IMG = 'https://secure.gravatar.com/avatar/{hash_str}?s={size}' +GRAVATAR_IMG = "https://secure.gravatar.com/avatar/{hash_str}?s={size}" @register.simple_tag @@ -23,6 +23,6 @@ def gravatar_img(user): def gravatar_url(user, size=PODCAST_LOGO_BIG_SIZE): email = user.email.strip().lower() m = hashlib.md5() - m.update(email.encode('utf-8')) + m.update(email.encode("utf-8")) gravatar_hash = m.hexdigest() return GRAVATAR_IMG.format(hash_str=gravatar_hash, size=size) diff --git a/mygpo/share/urls.py b/mygpo/share/urls.py index 98276f3c5..4358bb32c 100644 --- a/mygpo/share/urls.py +++ b/mygpo/share/urls.py @@ -5,72 +5,72 @@ from mygpo.users import converters -register_converter(converters.UsernameConverter, 'username') +register_converter(converters.UsernameConverter, "username") urlpatterns = [ - path('share/', views.overview, name='share'), + path("share/", views.overview, name="share"), path( - 'share/subscriptions-public', + "share/subscriptions-public", views.set_token_public, - kwargs={'public': True, 'token_name': 'subscriptions_token'}, - name='subscriptions-public', + kwargs={"public": True, "token_name": "subscriptions_token"}, + name="subscriptions-public", ), path( - 'share/subscriptions-private', + "share/subscriptions-private", views.set_token_public, - kwargs={'public': False, 'token_name': 'subscriptions_token'}, - name='subscriptions-private', + kwargs={"public": False, "token_name": "subscriptions_token"}, + name="subscriptions-private", ), path( - 'share/favfeed-public', + "share/favfeed-public", views.set_token_public, - kwargs={'public': True, 'token_name': 'favorite_feeds_token'}, - name='favfeed-public', + kwargs={"public": True, "token_name": "favorite_feeds_token"}, + name="favfeed-public", ), path( - 'share/favfeed-private', + "share/favfeed-private", views.set_token_public, - kwargs={'public': False, 'token_name': 'favorite_feeds_token'}, - name='favfeed-private', + kwargs={"public": False, "token_name": "favorite_feeds_token"}, + name="favfeed-private", ), path( - 'share/userpage-public', + "share/userpage-public", views.set_token_public, - kwargs={'public': True, 'token_name': 'userpage_token'}, - name='userpage-public', + kwargs={"public": True, "token_name": "userpage_token"}, + name="userpage-public", ), path( - 'share/userpage-private', + "share/userpage-private", views.set_token_public, - kwargs={'public': False, 'token_name': 'userpage_token'}, - name='userpage-private', + kwargs={"public": False, "token_name": "userpage_token"}, + name="userpage-private", ), - path('share/favorites', views.ShareFavorites.as_view(), name='share-favorites'), + path("share/favorites", views.ShareFavorites.as_view(), name="share-favorites"), path( - 'favorites/private', + "favorites/private", views.FavoritesPublic.as_view(public=False), - name='favorites_private', + name="favorites_private", ), path( - 'favorites/public', + "favorites/public", views.FavoritesPublic.as_view(public=True), - name='favorites_public', + name="favorites_public", ), path( - 'share/subscriptions/private', + "share/subscriptions/private", views.PublicSubscriptions.as_view(public=False), - name='private_subscriptions', + name="private_subscriptions", ), path( - 'share/subscriptions/public', + "share/subscriptions/public", views.PublicSubscriptions.as_view(public=True), - name='public_subscriptions', + name="public_subscriptions", ), path( - 'share/favorites/create-directory-entry', + "share/favorites/create-directory-entry", views.FavoritesFeedCreateEntry.as_view(), - name='favorites-create-entry', + name="favorites-create-entry", ), - path('user//', userpage.UserpageView.as_view(), name='user'), + path("user//", userpage.UserpageView.as_view(), name="user"), ] diff --git a/mygpo/share/userpage.py b/mygpo/share/userpage.py index e2ae24d0c..c78079112 100644 --- a/mygpo/share/userpage.py +++ b/mygpo/share/userpage.py @@ -25,7 +25,7 @@ class UserpageView(View): @method_decorator( requires_token( - token_name='userpage_token', denied_template='userpage-denied.html' + token_name="userpage_token", denied_template="userpage-denied.html" ) ) def get(self, request, username): @@ -36,21 +36,21 @@ def get(self, request, username): site = RequestSite(request) context = { - 'page_user': user, - 'site': site.domain, - 'subscriptions_token': user.profile.get_token('subscriptions_token'), - 'favorite_feeds_token': user.profile.get_token('favorite_feeds_token'), - 'lists': self.get_podcast_lists(user), - 'subscriptions': self.get_subscriptions(user), - 'recent_episodes': last_played_episodes(user), - 'seconds_played_total': seconds_played(user), - 'seconds_played_month': seconds_played(user, month_ago), - 'favorite_episodes': FavoriteEpisode.episodes_for_user(user), - 'num_played_episodes_total': num_played_episodes(user), - 'num_played_episodes_month': num_played_episodes(user, month_ago), + "page_user": user, + "site": site.domain, + "subscriptions_token": user.profile.get_token("subscriptions_token"), + "favorite_feeds_token": user.profile.get_token("favorite_feeds_token"), + "lists": self.get_podcast_lists(user), + "subscriptions": self.get_subscriptions(user), + "recent_episodes": last_played_episodes(user), + "seconds_played_total": seconds_played(user), + "seconds_played_month": seconds_played(user, month_ago), + "favorite_episodes": FavoriteEpisode.episodes_for_user(user), + "num_played_episodes_total": num_played_episodes(user), + "num_played_episodes_month": num_played_episodes(user, month_ago), } - return render(request, 'userpage.html', context) + return render(request, "userpage.html", context) def get_podcast_lists(self, user): return PodcastList.objects.filter(user=user) diff --git a/mygpo/share/views.py b/mygpo/share/views.py index ab19dec9b..76b9600c4 100644 --- a/mygpo/share/views.py +++ b/mygpo/share/views.py @@ -28,14 +28,14 @@ class FavoritesPublic(View): def post(self, request): if self.public: - request.user.profile.favorite_feeds_token = '' + request.user.profile.favorite_feeds_token = "" request.user.profile.save() else: - request.user.profile.create_new_token('favorite_feeds_token') + request.user.profile.create_new_token("favorite_feeds_token") request.user.profile.save() - return HttpResponseRedirect(reverse('share-favorites')) + return HttpResponseRedirect(reverse("share-favorites")) class ShareFavorites(View): @@ -55,8 +55,8 @@ def get(self, request): return render( request, - 'share/favorites.html', - {'feed_token': token, 'site': site, 'podcast': podcast}, + "share/favorites.html", + {"feed_token": token, "site": site, "podcast": podcast}, ) @@ -70,13 +70,13 @@ class PublicSubscriptions(View): def post(self, request): if self.public: - user.profile.subscriptions_token = '' + user.profile.subscriptions_token = "" else: - user.profile.create_new_token('subscriptions_token') + user.profile.create_new_token("subscriptions_token") user.profile.save() - return HttpResponseRedirect(reverse('share')) + return HttpResponseRedirect(reverse("share")) class FavoritesFeedCreateEntry(View): @@ -99,7 +99,7 @@ def post(self, request): updater = PodcastUpdater(feed_url) updater.update_podcast() - return HttpResponseRedirect(reverse('share-favorites')) + return HttpResponseRedirect(reverse("share-favorites")) @login_required @@ -107,9 +107,9 @@ def overview(request): user = request.user site = RequestSite(request) - subscriptions_token = user.profile.get_token('subscriptions_token') - userpage_token = user.profile.get_token('userpage_token') - favfeed_token = user.profile.get_token('favorite_feeds_token') + subscriptions_token = user.profile.get_token("subscriptions_token") + userpage_token = user.profile.get_token("userpage_token") + favfeed_token = user.profile.get_token("favorite_feeds_token") favfeed = FavoriteFeed(user) favfeed_url = favfeed.get_public_url(site.domain) @@ -117,13 +117,13 @@ def overview(request): return render( request, - 'share/overview.html', + "share/overview.html", { - 'site': site, - 'subscriptions_token': subscriptions_token, - 'userpage_token': userpage_token, - 'favfeed_token': favfeed_token, - 'favfeed_podcast': favfeed_podcast, + "site": site, + "subscriptions_token": subscriptions_token, + "userpage_token": userpage_token, + "favfeed_token": favfeed_token, + "favfeed_podcast": favfeed_podcast, }, ) @@ -134,11 +134,11 @@ def set_token_public(request, token_name, public): user = request.user if public: - setattr(user.profile, token_name, '') + setattr(user.profile, token_name, "") user.profile.save() else: user.profile.create_new_token(token_name) user.profile.save() - return HttpResponseRedirect(reverse('share')) + return HttpResponseRedirect(reverse("share")) diff --git a/mygpo/shell.py b/mygpo/shell.py index 6fd3c53ff..b44e61ac2 100644 --- a/mygpo/shell.py +++ b/mygpo/shell.py @@ -15,4 +15,4 @@ from django.utils.module_loading import import_string for m in apps.get_models(): - import_string('{module}.{model}'.format(module=m.__module__, model=m.__name__)) + import_string("{module}.{model}".format(module=m.__module__, model=m.__name__)) diff --git a/mygpo/subscriptions/__init__.py b/mygpo/subscriptions/__init__.py index f812a4b1e..974f0e20e 100644 --- a/mygpo/subscriptions/__init__.py +++ b/mygpo/subscriptions/__init__.py @@ -11,10 +11,10 @@ def get_subscribe_targets(podcast, user): - """ Clients / SyncGroup on which the podcast can be subscribed + """Clients / SyncGroup on which the podcast can be subscribed This excludes all devices/syncgroups on which the podcast is already - subscribed """ + subscribed""" # django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. from mygpo.users.models import Client @@ -22,7 +22,7 @@ def get_subscribe_targets(podcast, user): clients = ( Client.objects.filter(user=user) .exclude(subscription__podcast=podcast, subscription__user=user) - .select_related('sync_group') + .select_related("sync_group") ) targets = set() @@ -36,19 +36,19 @@ def get_subscribe_targets(podcast, user): def get_subscribed_podcasts(user, only_public=False): - """ Returns all subscribed podcasts for the user + """Returns all subscribed podcasts for the user The attribute "url" contains the URL that was used when subscribing to - the podcast """ + the podcast""" # django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. from mygpo.subscriptions.models import Subscription, SubscribedPodcast subscriptions = ( Subscription.objects.filter(user=user) - .order_by('podcast') - .distinct('podcast') - .select_related('podcast') + .order_by("podcast") + .distinct("podcast") + .select_related("podcast") ) # django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. @@ -74,7 +74,7 @@ def get_subscribed_podcasts(user, only_public=False): def get_subscription_history( user, client=None, since=None, until=None, public_only=False ): - """ Returns chronologically ordered subscription history entries + """Returns chronologically ordered subscription history entries Setting device_id restricts the actions to a certain device """ @@ -82,30 +82,30 @@ def get_subscription_history( # django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. from mygpo.history.models import SUBSCRIPTION_ACTIONS, HistoryEntry - logger.info('Subscription History for {user}'.format(user=user.username)) + logger.info("Subscription History for {user}".format(user=user.username)) history = ( HistoryEntry.objects.filter(user=user) .filter(action__in=SUBSCRIPTION_ACTIONS) - .order_by('timestamp') + .order_by("timestamp") ) if client: - logger.info(u'... client {client_uid}'.format(client_uid=client.uid)) + logger.info(u"... client {client_uid}".format(client_uid=client.uid)) history = history.filter(client=client) if since: - logger.info('... since {since}'.format(since=since)) + logger.info("... since {since}".format(since=since)) history = history.filter(timestamp__gt=since) if until: - logger.info('... until {until}'.format(until=until)) + logger.info("... until {until}".format(until=until)) history = history.filter(timestamp__lte=until) if public_only: # django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. from mygpo.usersettings.models import UserSettings - logger.info('... only public') + logger.info("... only public") private = UserSettings.objects.get_private_podcasts(user) history = history.exclude(podcast__in=private) @@ -113,7 +113,7 @@ def get_subscription_history( def get_subscription_change_history(history): - """ Actions that added/removed podcasts from the subscription list + """Actions that added/removed podcasts from the subscription list Returns an iterator of all subscription actions that either * added subscribed a podcast that hasn't been subscribed directly diff --git a/mygpo/subscriptions/admin.py b/mygpo/subscriptions/admin.py index 69d433abc..5eb1d8c8f 100644 --- a/mygpo/subscriptions/admin.py +++ b/mygpo/subscriptions/admin.py @@ -8,11 +8,11 @@ class SubscriptionAdmin(admin.ModelAdmin): """ Admin page for subscriptions """ # configuration for the list view - list_display = ('user', 'podcast', 'client') + list_display = ("user", "podcast", "client") # fetch the related objects for the fields in list_display - list_select_related = ('user', 'podcast', 'client') + list_select_related = ("user", "podcast", "client") - raw_id_fields = ('user', 'podcast', 'client') + raw_id_fields = ("user", "podcast", "client") show_full_result_count = False diff --git a/mygpo/subscriptions/migrations/0001_initial.py b/mygpo/subscriptions/migrations/0001_initial.py index ea0ff08dc..da1c0de01 100644 --- a/mygpo/subscriptions/migrations/0001_initial.py +++ b/mygpo/subscriptions/migrations/0001_initial.py @@ -9,73 +9,73 @@ class Migration(migrations.Migration): dependencies = [ - ('users', '0007_syncgroup_protect'), + ("users", "0007_syncgroup_protect"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('podcasts', '0023_auto_20140729_1711'), + ("podcasts", "0023_auto_20140729_1711"), ] operations = [ migrations.CreateModel( - name='PodcastConfig', + name="PodcastConfig", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('settings', models.TextField(default='{}')), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), + ("settings", models.TextField(default="{}")), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), ( - 'podcast', + "podcast", models.ForeignKey( - to='podcasts.Podcast', + to="podcasts.Podcast", on_delete=django.db.models.deletion.PROTECT, ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), ), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='Subscription', + name="Subscription", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('deleted', models.BooleanField(default=False)), - ('ref_url', models.URLField(max_length=2048)), - ('created', models.DateTimeField()), - ('modified', models.DateTimeField()), + ("deleted", models.BooleanField(default=False)), + ("ref_url", models.URLField(max_length=2048)), + ("created", models.DateTimeField()), + ("modified", models.DateTimeField()), ( - 'client', - models.ForeignKey(to='users.Client', on_delete=models.CASCADE), + "client", + models.ForeignKey(to="users.Client", on_delete=models.CASCADE), ), ( - 'podcast', + "podcast", models.ForeignKey( - to='podcasts.Podcast', + to="podcasts.Podcast", on_delete=django.db.models.deletion.PROTECT, ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -85,9 +85,9 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='subscription', unique_together=set([('user', 'client', 'podcast')]) + name="subscription", unique_together=set([("user", "client", "podcast")]) ), migrations.AlterIndexTogether( - name='subscription', index_together=set([('user', 'client')]) + name="subscription", index_together=set([("user", "client")]) ), ] diff --git a/mygpo/subscriptions/migrations/0002_unique_constraint.py b/mygpo/subscriptions/migrations/0002_unique_constraint.py index 4acc9db97..06b415b31 100644 --- a/mygpo/subscriptions/migrations/0002_unique_constraint.py +++ b/mygpo/subscriptions/migrations/0002_unique_constraint.py @@ -6,10 +6,10 @@ class Migration(migrations.Migration): - dependencies = [('subscriptions', '0001_initial')] + dependencies = [("subscriptions", "0001_initial")] operations = [ migrations.AlterUniqueTogether( - name='podcastconfig', unique_together=set([('user', 'podcast')]) + name="podcastconfig", unique_together=set([("user", "podcast")]) ) ] diff --git a/mygpo/subscriptions/migrations/0003_remove_podcastconfig.py b/mygpo/subscriptions/migrations/0003_remove_podcastconfig.py index f82b7839d..7481de1da 100644 --- a/mygpo/subscriptions/migrations/0003_remove_podcastconfig.py +++ b/mygpo/subscriptions/migrations/0003_remove_podcastconfig.py @@ -7,14 +7,14 @@ class Migration(migrations.Migration): dependencies = [ - ('subscriptions', '0002_unique_constraint'), + ("subscriptions", "0002_unique_constraint"), # ensure that data migration has been executed before model is deleted - ('usersettings', '0002_move_existing'), + ("usersettings", "0002_move_existing"), ] operations = [ - migrations.AlterUniqueTogether(name='podcastconfig', unique_together=None), - migrations.RemoveField(model_name='podcastconfig', name='podcast'), - migrations.RemoveField(model_name='podcastconfig', name='user'), - migrations.DeleteModel(name='PodcastConfig'), + migrations.AlterUniqueTogether(name="podcastconfig", unique_together=None), + migrations.RemoveField(model_name="podcastconfig", name="podcast"), + migrations.RemoveField(model_name="podcastconfig", name="user"), + migrations.DeleteModel(name="PodcastConfig"), ] diff --git a/mygpo/subscriptions/migrations/0004_subscription_index.py b/mygpo/subscriptions/migrations/0004_subscription_index.py index fb29e41be..d372330cc 100644 --- a/mygpo/subscriptions/migrations/0004_subscription_index.py +++ b/mygpo/subscriptions/migrations/0004_subscription_index.py @@ -4,11 +4,11 @@ class Migration(migrations.Migration): - dependencies = [('subscriptions', '0003_remove_podcastconfig')] + dependencies = [("subscriptions", "0003_remove_podcastconfig")] operations = [ migrations.AlterIndexTogether( - name='subscription', - index_together=set([('podcast', 'user'), ('user', 'client')]), + name="subscription", + index_together=set([("podcast", "user"), ("user", "client")]), ) ] diff --git a/mygpo/subscriptions/models.py b/mygpo/subscriptions/models.py index 0d811f486..be114bc88 100644 --- a/mygpo/subscriptions/models.py +++ b/mygpo/subscriptions/models.py @@ -34,16 +34,16 @@ class Subscription(DeleteableModel): modified = models.DateTimeField() class Meta: - unique_together = [['user', 'client', 'podcast']] + unique_together = [["user", "client", "podcast"]] - index_together = [['user', 'client'], ['podcast', 'user']] + index_together = [["user", "client"], ["podcast", "user"]] def __str__(self): - return '{user} subscribed to {podcast} on {client}'.format( + return "{user} subscribed to {podcast} on {client}".format( user=self.user, podcast=self.podcast, client=self.client ) SubscribedPodcast = collections.namedtuple( - 'SubscribedPodcast', 'podcast public ref_url' + "SubscribedPodcast", "podcast public ref_url" ) diff --git a/mygpo/subscriptions/tasks.py b/mygpo/subscriptions/tasks.py index 23d81a17b..ee0c3d53f 100644 --- a/mygpo/subscriptions/tasks.py +++ b/mygpo/subscriptions/tasks.py @@ -1,10 +1,12 @@ from datetime import datetime +from django.contrib.auth import get_user_model from django.db import transaction from mygpo.subscriptions.models import Subscription from mygpo.subscriptions.signals import subscription_changed from mygpo.history.models import HistoryEntry +from mygpo.podcasts.models import Podcast from mygpo.utils import to_maxlength from mygpo.celery import celery @@ -14,10 +16,17 @@ @celery.task(max_retries=5, default_retry_delay=60) -def subscribe(podcast, user, client, ref_url=None): - """ subscribes user to the current podcast on one client +def subscribe(podcast_pk, user_pk, client_uid, ref_url=None): + """subscribes user to the current podcast on one client + + Takes syned devices into account.""" + podcast = Podcast.objects.get(pk=podcast_pk) + + User = get_user_model() + user = User.objects.get(pk=user_pk) + + client = user.client_set.get(uid=client_uid) - Takes syned devices into account. """ ref_url = ref_url or podcast.url now = datetime.utcnow() clients = _affected_clients(client) @@ -28,10 +37,17 @@ def subscribe(podcast, user, client, ref_url=None): @celery.task(max_retries=5, default_retry_delay=60) -def unsubscribe(podcast, user, client): - """ unsubscribes user from the current podcast on one client +def unsubscribe(podcast_pk, user_pk, client_uid): + """unsubscribes user from the current podcast on one client + + Takes syned devices into account.""" + podcast = Podcast.objects.get(pk=podcast_pk) + + User = get_user_model() + user = User.objects.get(pk=user_pk) + + client = user.client_set.get(uid=client_uid) - Takes syned devices into account. """ now = datetime.utcnow() clients = _affected_clients(client) @@ -42,8 +58,13 @@ def unsubscribe(podcast, user, client): @celery.task(max_retries=5, default_retry_delay=60) -def subscribe_all(podcast, user, ref_url=None): +def subscribe_all(podcast_pk, user_pk, ref_url=None): """ subscribes user to the current podcast on all clients """ + podcast = Podcast.objects.get(pk=podcast_pk) + + User = get_user_model() + user = User.objects.get(pk=user_pk) + ref_url = ref_url or podcast.url now = datetime.utcnow() clients = user.client_set.all() @@ -54,8 +75,13 @@ def subscribe_all(podcast, user, ref_url=None): @celery.task(max_retries=5, default_retry_delay=60) -def unsubscribe_all(podcast, user): +def unsubscribe_all(podcast_pk, user_pk): """ unsubscribes user from the current podcast on all clients """ + podcast = Podcast.objects.get(pk=podcast_pk) + + User = get_user_model() + user = User.objects.get(pk=user_pk) + now = datetime.utcnow() clients = user.client_set.filter(subscription__podcast=podcast) @@ -66,10 +92,10 @@ def unsubscribe_all(podcast, user): @transaction.atomic def _perform_subscribe(podcast, user, clients, timestamp, ref_url): - """ Subscribes to a podcast on multiple clients + """Subscribes to a podcast on multiple clients Yields the clients on which a subscription was added, ie not those where - the subscription already existed. """ + the subscription already existed.""" for client in clients: subscription, created = Subscription.objects.get_or_create( @@ -77,9 +103,9 @@ def _perform_subscribe(podcast, user, clients, timestamp, ref_url): client=client, podcast=podcast, defaults={ - 'ref_url': to_maxlength(Subscription, 'ref_url', ref_url), - 'created': timestamp, - 'modified': timestamp, + "ref_url": to_maxlength(Subscription, "ref_url", ref_url), + "created": timestamp, + "modified": timestamp, }, ) @@ -87,7 +113,7 @@ def _perform_subscribe(podcast, user, clients, timestamp, ref_url): continue logger.info( - '{user} subscribed to {podcast} on {client}'.format( + "{user} subscribed to {podcast} on {client}".format( user=user, podcast=podcast, client=client ) ) @@ -105,10 +131,10 @@ def _perform_subscribe(podcast, user, clients, timestamp, ref_url): @transaction.atomic def _perform_unsubscribe(podcast, user, clients, timestamp): - """ Unsubscribes from a podcast on multiple clients + """Unsubscribes from a podcast on multiple clients Yields the clients on which a subscription was removed, ie not those where - the podcast was not subscribed. """ + the podcast was not subscribed.""" for client in clients: @@ -122,7 +148,7 @@ def _perform_unsubscribe(podcast, user, clients, timestamp): subscription.delete() logger.info( - '{user} unsubscribed from {podcast} on {client}'.format( + "{user} unsubscribed from {podcast} on {client}".format( user=user, podcast=podcast, client=client ) ) diff --git a/mygpo/subscriptions/tests.py b/mygpo/subscriptions/tests.py index af914964c..930722e6f 100644 --- a/mygpo/subscriptions/tests.py +++ b/mygpo/subscriptions/tests.py @@ -16,13 +16,13 @@ class TestSubscribe(TestCase): def setUp(self): User = get_user_model() self.user = User( - username='duplicate-subscribe', email='duplicate-subscribe@example.com' + username="duplicate-subscribe", email="duplicate-subscribe@example.com" ) - self.user.set_password('secret') + self.user.set_password("secret") self.user.save() - self.client = Client.objects.create(user=self.user, uid='dev1', id=uuid.uuid1()) + self.client = Client.objects.create(user=self.user, uid="dev1", id=uuid.uuid1()) - self.url = 'http://www.example.com/pdocast.rss' + self.url = "http://www.example.com/pdocast.rss" self.podcast = Podcast.objects.get_or_create_for_url(self.url).object def test_duplicate_subscribe(self): diff --git a/mygpo/subscriptions/urls.py b/mygpo/subscriptions/urls.py index aa75d71c6..8961ad3c8 100644 --- a/mygpo/subscriptions/urls.py +++ b/mygpo/subscriptions/urls.py @@ -5,25 +5,25 @@ from mygpo.users import converters -register_converter(converters.UsernameConverter, 'username') +register_converter(converters.UsernameConverter, "username") urlpatterns = [ - path('subscriptions/', views.show_list, name='subscriptions'), - path('download/subscriptions.opml', views.download_all, name='subscriptions-opml'), + path("subscriptions/", views.show_list, name="subscriptions"), + path("download/subscriptions.opml", views.download_all, name="subscriptions-opml"), path( - 'user//subscriptions/rss/', + "user//subscriptions/rss/", views.subscriptions_feed, - name='shared-subscriptions-rss', + name="shared-subscriptions-rss", ), path( - 'user//subscriptions', + "user//subscriptions", views.for_user, - name='shared-subscriptions', + name="shared-subscriptions", ), path( - 'user//subscriptions.opml', + "user//subscriptions.opml", views.for_user_opml, - name='shared-subscriptions-opml', + name="shared-subscriptions-opml", ), ] diff --git a/mygpo/subscriptions/views.py b/mygpo/subscriptions/views.py index ea75e73bc..17bf55e61 100644 --- a/mygpo/subscriptions/views.py +++ b/mygpo/subscriptions/views.py @@ -37,11 +37,11 @@ def show_list(request): subscriptionlist = create_subscriptionlist(request) return render( request, - 'subscriptions.html', + "subscriptions.html", { - 'subscriptionlist': subscriptionlist, - 'url': current_site, - 'podcast_ad': Podcast.objects.get_advertised_podcast(), + "subscriptionlist": subscriptionlist, + "url": current_site, + "podcast_ad": Podcast.objects.get_advertised_podcast(), }, ) @@ -51,8 +51,8 @@ def show_list(request): @login_required def download_all(request): podcasts = get_subscribed_podcasts(request.user) - response = simple.format_podcast_list(podcasts, 'opml', request.user.username) - response['Content-Disposition'] = 'attachment; filename=all-subscriptions.opml' + response = simple.format_podcast_list(podcasts, "opml", request.user.username) + response["Content-Disposition"] = "attachment; filename=all-subscriptions.opml" return response @@ -63,7 +63,7 @@ def create_subscriptionlist(request): subscriptions = ( Subscription.objects.filter(user=user) .exclude(deleted=True) - .select_related('podcast', 'client') + .select_related("podcast", "client") ) # grou clients by subscribed podcasts @@ -73,29 +73,29 @@ def create_subscriptionlist(request): if not podcast in subscription_list: subscription_list[podcast] = { - 'podcast': podcast, - 'devices': [], - 'episodes': podcast.episode_count, + "podcast": podcast, + "devices": [], + "episodes": podcast.episode_count, } - subscription_list[podcast]['devices'].append(subscription.client) + subscription_list[podcast]["devices"].append(subscription.client) # sort most recently updated podcast first subscriptions = subscription_list.values() now = datetime.utcnow() - sort_key = lambda s: s['podcast'].latest_episode_timestamp or now + sort_key = lambda s: s["podcast"].latest_episode_timestamp or now subscriptions = sorted(subscriptions, key=sort_key, reverse=True) return subscriptions -@requires_token(token_name='subscriptions_token') +@requires_token(token_name="subscriptions_token") def subscriptions_feed(request, username): # Create to feed manually so we can wrap the token-authentication around it f = SubscriptionsFeed(username) obj = f.get_object(request, username) feedgen = f.get_feed(obj, request) response = HttpResponse(content_type=feedgen.content_type) - feedgen.write(response, 'utf-8') + feedgen.write(response, "utf-8") return response @@ -114,17 +114,17 @@ def get_object(self, request, username): return user def title(self, user): - return _('%(username)s\'s Podcast Subscriptions on %(site)s') % dict( + return _("%(username)s's Podcast Subscriptions on %(site)s") % dict( username=user.username, site=self.site ) def description(self, user): return _( - 'Recent changes to %(username)s\'s podcast subscriptions on %(site)s' + "Recent changes to %(username)s's podcast subscriptions on %(site)s" ) % dict(username=user.username, site=self.site) def link(self, user): - return reverse('shared-subscriptions', args=[user.username]) + return reverse("shared-subscriptions", args=[user.username]) def items(self, user): history = get_subscription_history(user, public_only=True) @@ -136,17 +136,17 @@ def author_name(self, user): return user.username def author_link(self, user): - return reverse('shared-subscriptions', args=[user.username]) + return reverse("shared-subscriptions", args=[user.username]) # entry-specific data below description_template = "subscription-feed-description.html" def item_title(self, entry): - if entry.action == 'subscribe': - s = _('%(username)s subscribed to %(podcast)s (%(site)s)') + if entry.action == "subscribe": + s = _("%(username)s subscribed to %(podcast)s (%(site)s)") else: - s = _('%(username)s unsubscribed from %(podcast)s (%(site)s)') + s = _("%(username)s unsubscribed from %(podcast)s (%(site)s)") return s % dict( username=self.username, podcast=entry.podcast.display_title, site=self.site @@ -160,36 +160,36 @@ def item_pubdate(self, item): @requires_token( - token_name='subscriptions_token', denied_template='user_subscriptions_denied.html' + token_name="subscriptions_token", denied_template="user_subscriptions_denied.html" ) def for_user(request, username): User = get_user_model() user = get_object_or_404(User, username=username) subscriptions = get_subscribed_podcasts(user, only_public=True) - token = user.profile.get_token('subscriptions_token') + token = user.profile.get_token("subscriptions_token") return render( request, - 'user_subscriptions.html', - {'subscriptions': subscriptions, 'other_user': user, 'token': token}, + "user_subscriptions.html", + {"subscriptions": subscriptions, "other_user": user, "token": token}, ) -@requires_token(token_name='subscriptions_token') +@requires_token(token_name="subscriptions_token") def for_user_opml(request, username): User = get_user_model() user = get_object_or_404(User, username=username) subscriptions = get_subscribed_podcasts(user, only_public=True) - if parse_bool(request.GET.get('symbian', False)): + if parse_bool(request.GET.get("symbian", False)): subscriptions = map(symbian_opml_changes, [p.podcast for p in subscriptions]) response = render( request, - 'user_subscriptions.opml', - {'subscriptions': subscriptions, 'other_user': user}, + "user_subscriptions.opml", + {"subscriptions": subscriptions, "other_user": user}, ) - response['Content-Disposition'] = ( - 'attachment; filename=%s-subscriptions.opml' % username + response["Content-Disposition"] = ( + "attachment; filename=%s-subscriptions.opml" % username ) return response diff --git a/mygpo/suggestions/__init__.py b/mygpo/suggestions/__init__.py index 84158da17..9db4822f4 100644 --- a/mygpo/suggestions/__init__.py +++ b/mygpo/suggestions/__init__.py @@ -1 +1 @@ -default_app_config = 'mygpo.suggestions.apps.SuggestionsConfig' +default_app_config = "mygpo.suggestions.apps.SuggestionsConfig" diff --git a/mygpo/suggestions/admin.py b/mygpo/suggestions/admin.py index 23b85c9a4..b232af5d0 100644 --- a/mygpo/suggestions/admin.py +++ b/mygpo/suggestions/admin.py @@ -8,11 +8,11 @@ class PodcastSuggestionAdmin(admin.ModelAdmin): """ Admin page for suggestions """ # configuration for the list view - list_display = ('suggested_to', 'podcast', 'deleted') + list_display = ("suggested_to", "podcast", "deleted") # fetch the related objects for the fields in list_display - list_select_related = ('suggested_to', 'podcast') + list_select_related = ("suggested_to", "podcast") - raw_id_fields = ('suggested_to', 'podcast') + raw_id_fields = ("suggested_to", "podcast") show_full_result_count = False diff --git a/mygpo/suggestions/apps.py b/mygpo/suggestions/apps.py index 42ceba412..d18d03e4e 100644 --- a/mygpo/suggestions/apps.py +++ b/mygpo/suggestions/apps.py @@ -8,16 +8,16 @@ def update_suggestions_on_subscription(sender, **kwargs): """ update a user's suggestions after one of his subscriptions change """ from mygpo.suggestions.tasks import update_suggestions - user = kwargs['user'] + user = kwargs["user"] # update_suggestions.delay(user.pk) class SuggestionsConfig(AppConfig): - name = 'mygpo.suggestions' + name = "mygpo.suggestions" verbose_name = "Suggestions" def ready(self): - Podcast = apps.get_model('podcasts.Podcast') + Podcast = apps.get_model("podcasts.Podcast") subscription_changed.connect(update_suggestions_on_subscription, sender=Podcast) diff --git a/mygpo/suggestions/migrations/0001_initial.py b/mygpo/suggestions/migrations/0001_initial.py index 3eb6456c5..38e4e491a 100644 --- a/mygpo/suggestions/migrations/0001_initial.py +++ b/mygpo/suggestions/migrations/0001_initial.py @@ -9,35 +9,35 @@ class Migration(migrations.Migration): dependencies = [ - ('podcasts', '0028_episode_indexes'), + ("podcasts", "0028_episode_indexes"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='PodcastSuggestion', + name="PodcastSuggestion", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('deleted', models.BooleanField(default=False)), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), + ("deleted", models.BooleanField(default=False)), ( - 'podcast', + "podcast", models.ForeignKey( - to='podcasts.Podcast', + to="podcasts.Podcast", on_delete=django.db.models.deletion.PROTECT, ), ), ( - 'suggested_to', + "suggested_to", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -47,6 +47,6 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='podcastsuggestion', unique_together=set([('suggested_to', 'podcast')]) + name="podcastsuggestion", unique_together=set([("suggested_to", "podcast")]) ), ] diff --git a/mygpo/suggestions/models.py b/mygpo/suggestions/models.py index 548dd37b2..857ce0477 100644 --- a/mygpo/suggestions/models.py +++ b/mygpo/suggestions/models.py @@ -6,9 +6,9 @@ class PodcastSuggestion(UpdateInfoModel, DeleteableModel): - """ A podcast which is suggested to a user + """A podcast which is suggested to a user - A suggestion can be marked as "unwanted" by a user by deleting it. """ + A suggestion can be marked as "unwanted" by a user by deleting it.""" # the user to which the podcast has been suggested suggested_to = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) @@ -17,4 +17,4 @@ class PodcastSuggestion(UpdateInfoModel, DeleteableModel): podcast = models.ForeignKey(Podcast, on_delete=models.PROTECT) class Meta: - unique_together = [('suggested_to', 'podcast')] + unique_together = [("suggested_to", "podcast")] diff --git a/mygpo/suggestions/tasks.py b/mygpo/suggestions/tasks.py index 6ffa881b8..130f35a4a 100644 --- a/mygpo/suggestions/tasks.py +++ b/mygpo/suggestions/tasks.py @@ -22,12 +22,12 @@ def update_suggestions(user_pk, max_suggestions=15): User = get_user_model() user = User.objects.get(pk=user_pk) - logger.info('Updating suggestions of user {user.username}'.format(user=user)) + logger.info("Updating suggestions of user {user.username}".format(user=user)) # calculate possible suggestions subscribed_podcasts = [sp.podcast for sp in get_subscribed_podcasts(user)] logger.debug( - 'Found {num_podcasts} subscribed podcasts'.format( + "Found {num_podcasts} subscribed podcasts".format( num_podcasts=len(subscribed_podcasts) ) ) @@ -41,7 +41,7 @@ def update_suggestions(user_pk, max_suggestions=15): # get most relevant counter = Counter(related) logger.debug( - 'Found {num_related} related podcasts'.format(num_related=len(counter)) + "Found {num_related} related podcasts".format(num_related=len(counter)) ) suggested = [p for p, count in counter.most_common(max_suggestions)] @@ -52,7 +52,7 @@ def update_suggestions(user_pk, max_suggestions=15): ) if created: logger.info( - 'Created suggestion for {podcast}'.format(podcast=suggested_podcast) + "Created suggestion for {podcast}".format(podcast=suggested_podcast) ) user.profile.suggestions_up_to_date = True diff --git a/mygpo/suggestions/urls.py b/mygpo/suggestions/urls.py index 574139a04..aa5be61d1 100644 --- a/mygpo/suggestions/urls.py +++ b/mygpo/suggestions/urls.py @@ -4,13 +4,13 @@ urlpatterns = [ - path('', views.suggestions, name='suggestions'), + path("", views.suggestions, name="suggestions"), path( - 'blacklist/', views.blacklist_slug, name='suggestions-blacklist-slug' + "blacklist/", views.blacklist_slug, name="suggestions-blacklist-slug" ), path( - 'blacklist/', + "blacklist/", views.blacklist_id, - name='suggestions-blacklist-id', + name="suggestions-blacklist-id", ), ] diff --git a/mygpo/suggestions/views.py b/mygpo/suggestions/views.py index c31c0de1d..8a5cbc51a 100644 --- a/mygpo/suggestions/views.py +++ b/mygpo/suggestions/views.py @@ -29,7 +29,7 @@ def blacklist(request, blacklisted_podcast): suggestion = PodcastSuggestion.objects.filter( suggested_to=user, podcast=blacklisted_podcast ).update(deleted=True) - return HttpResponseRedirect(reverse('suggestions')) + return HttpResponseRedirect(reverse("suggestions")) @vary_on_cookie @@ -42,7 +42,7 @@ def suggestions(request): ) current_site = RequestSite(request) return render( - request, 'suggestions.html', {'entries': suggestions, 'url': current_site} + request, "suggestions.html", {"entries": suggestions, "url": current_site} ) diff --git a/mygpo/test.py b/mygpo/test.py index bf9708aea..5655598ae 100644 --- a/mygpo/test.py +++ b/mygpo/test.py @@ -9,16 +9,14 @@ def create_auth_string(username, password): - pwdstr = '{0}:{1}'.format(username, password).rstrip() - credentials = base64.b64encode(pwdstr.encode('utf-8')) - auth_string = 'Basic ' + credentials.decode('ascii') + pwdstr = "{0}:{1}".format(username, password).rstrip() + credentials = base64.b64encode(pwdstr.encode("utf-8")) + auth_string = "Basic " + credentials.decode("ascii") return auth_string def anon_request(url): - """ Emulates an anonymous request, returns the response - - """ + """Emulates an anonymous request, returns the response""" factory = RequestFactory() request = factory.get(url) request.user = AnonymousUser() @@ -33,7 +31,7 @@ def create_user(): User = get_user_model() password = random_token(10) username = random_token(8) - user = User(username=username, email=username + '@example.com') + user = User(username=username, email=username + "@example.com") user.set_password(password) user.is_active = True user.save() diff --git a/mygpo/urls.py b/mygpo/urls.py index 31c531b29..425b3e5a7 100644 --- a/mygpo/urls.py +++ b/mygpo/urls.py @@ -15,24 +15,24 @@ if settings.MAINTENANCE: from mygpo.web import utils - urlpatterns += [re_path('', utils.maintenance)] + urlpatterns += [re_path("", utils.maintenance)] # URLs are still registered during maintenace mode because we need to # build links from them (eg login-link). urlpatterns += [ - path('', include('mygpo.web.urls')), - path('', include('mygpo.podcasts.urls')), - path('', include('mygpo.directory.urls')), - path('', include('mygpo.api.urls')), - path('', include('mygpo.userfeeds.urls')), - path('', include('mygpo.share.urls')), - path('', include('mygpo.history.urls')), - path('', include('mygpo.subscriptions.urls')), - path('', include('mygpo.users.urls')), - path('', include('mygpo.podcastlists.urls')), - path('suggestions/', include('mygpo.suggestions.urls')), - path('publisher/', include('mygpo.publisher.urls')), - path('administration/', include('mygpo.administration.urls')), - path('pubsub/', include('mygpo.pubsub.urls')), - path('admin/', admin.site.urls), + path("", include("mygpo.web.urls")), + path("", include("mygpo.podcasts.urls")), + path("", include("mygpo.directory.urls")), + path("", include("mygpo.api.urls")), + path("", include("mygpo.userfeeds.urls")), + path("", include("mygpo.share.urls")), + path("", include("mygpo.history.urls")), + path("", include("mygpo.subscriptions.urls")), + path("", include("mygpo.users.urls")), + path("", include("mygpo.podcastlists.urls")), + path("suggestions/", include("mygpo.suggestions.urls")), + path("publisher/", include("mygpo.publisher.urls")), + path("administration/", include("mygpo.administration.urls")), + path("pubsub/", include("mygpo.pubsub.urls")), + path("admin/", admin.site.urls), ] diff --git a/mygpo/userfeeds/auth.py b/mygpo/userfeeds/auth.py index aa27eaf2d..ea7e1a597 100644 --- a/mygpo/userfeeds/auth.py +++ b/mygpo/userfeeds/auth.py @@ -12,19 +12,19 @@ def view_or_basicauth(view, request, username, token_name, realm="", *args, **kw User = get_user_model() user = get_object_or_404(User, username=username) - token = getattr(user, token_name, '') + token = getattr(user, token_name, "") # check if a token is required at all - if token == '': + if token == "": return view(request, username, *args, **kwargs) # this header format is used when passing auth-headers # from Aapache to fcgi - if 'AUTHORIZATION' in request.META: - auth = request.META['AUTHORIZATION'] + if "AUTHORIZATION" in request.META: + auth = request.META["AUTHORIZATION"] - elif 'HTTP_AUTHORIZATION' in request.META: - auth = request.META['HTTP_AUTHORIZATION'] + elif "HTTP_AUTHORIZATION" in request.META: + auth = request.META["HTTP_AUTHORIZATION"] else: return auth_request() @@ -35,8 +35,8 @@ def view_or_basicauth(view, request, username, token_name, realm="", *args, **kw auth_type, credentials = auth # NOTE: We are only support basic authentication for now. - if auth_type.lower() == 'basic': - credentials = credentials.decode('base64').split(':', 1) + if auth_type.lower() == "basic": + credentials = credentials.decode("base64").split(":", 1) if len(credentials) == 2: uname, passwd = credentials @@ -50,13 +50,13 @@ def view_or_basicauth(view, request, username, token_name, realm="", *args, **kw return auth_request() -def auth_request(realm=''): +def auth_request(realm=""): # Either they did not provide an authorization header or # something in the authorization attempt failed. Send a 401 # back to them to ask them to authenticate. response = HttpResponse() response.status_code = 401 - response['WWW-Authenticate'] = 'Basic realm="%s"' % realm + response["WWW-Authenticate"] = 'Basic realm="%s"' % realm return response @@ -67,7 +67,7 @@ def wrapper(protected_view): @wraps(protected_view) def tmp(request, username, *args, **kwargs): return view_or_basicauth( - protected_view, request, username, token_name, '', *args, **kwargs + protected_view, request, username, token_name, "", *args, **kwargs ) return tmp diff --git a/mygpo/userfeeds/feeds.py b/mygpo/userfeeds/feeds.py index 851caa8d5..abaafc1f2 100644 --- a/mygpo/userfeeds/feeds.py +++ b/mygpo/userfeeds/feeds.py @@ -8,7 +8,7 @@ def __init__(self, user): self.user = user def title(self): - return '%s\'s Favorite Episodes' % self.user.username + return "%s's Favorite Episodes" % self.user.username def get_episodes(self): return FavoriteEpisode.episodes_for_user(self.user) @@ -22,10 +22,10 @@ def language(self): if len(l) == 1: return l[0] else: - return '' + return "" def get_public_url(self, domain): - return 'http://%s%s' % ( + return "http://%s%s" % ( domain, - reverse('favorites-feed', args=[self.user.username]), + reverse("favorites-feed", args=[self.user.username]), ) diff --git a/mygpo/userfeeds/urls.py b/mygpo/userfeeds/urls.py index 9b9e35ed7..28d1ebaf2 100644 --- a/mygpo/userfeeds/urls.py +++ b/mygpo/userfeeds/urls.py @@ -5,13 +5,13 @@ from mygpo.users import converters -register_converter(converters.UsernameConverter, 'username') +register_converter(converters.UsernameConverter, "username") urlpatterns = [ path( - 'user//favorites.xml', + "user//favorites.xml", views.favorite_feed, - name='favorites-feed', + name="favorites-feed", ) ] diff --git a/mygpo/userfeeds/views.py b/mygpo/userfeeds/views.py index 8f50b4ca8..fb557a47d 100644 --- a/mygpo/userfeeds/views.py +++ b/mygpo/userfeeds/views.py @@ -7,7 +7,7 @@ from mygpo.userfeeds.feeds import FavoriteFeed -@require_token_auth('favorite_feeds_token') +@require_token_auth("favorite_feeds_token") def favorite_feed(request, username): site = RequestSite(request) @@ -19,7 +19,7 @@ def favorite_feed(request, username): return render( request, - 'userfeed.xml', - {'site': site, 'feed_user': user, 'feed': feed}, - content_type='text/xml', + "userfeed.xml", + {"site": site, "feed_user": user, "feed": feed}, + content_type="text/xml", ) diff --git a/mygpo/users/__init__.py b/mygpo/users/__init__.py index 7529e2298..9f66f8bd0 100644 --- a/mygpo/users/__init__.py +++ b/mygpo/users/__init__.py @@ -1,3 +1,3 @@ -default_app_config = 'mygpo.users.apps.UsersConfig' +default_app_config = "mygpo.users.apps.UsersConfig" from . import checks diff --git a/mygpo/users/admin.py b/mygpo/users/admin.py index 254ae58b0..81820117e 100644 --- a/mygpo/users/admin.py +++ b/mygpo/users/admin.py @@ -9,7 +9,7 @@ class UserProfileInline(admin.StackedInline): model = UserProfile can_delete = False - verbose_name_plural = 'profile' + verbose_name_plural = "profile" # Define a new User admin @@ -17,13 +17,13 @@ class UserAdmin(UserAdmin): inlines = (UserProfileInline,) list_display = ( - 'username', - 'email', - 'is_active', - 'is_staff', - 'is_superuser', - 'date_joined', - 'last_login', + "username", + "email", + "is_active", + "is_staff", + "is_superuser", + "date_joined", + "last_login", ) @@ -37,15 +37,15 @@ class ClientAdmin(admin.ModelAdmin): """ Admin page for clients """ # configuration for the list view - list_display = ('name', 'user', 'uid', 'type') + list_display = ("name", "user", "uid", "type") # fetch the client's user for the fields in list_display - list_select_related = ('user',) + list_select_related = ("user",) - list_filter = ('type',) - search_fields = ('name', 'uid', 'user__username') + list_filter = ("type",) + search_fields = ("name", "uid", "user__username") - raw_id_fields = ('user',) + raw_id_fields = ("user",) show_full_result_count = False @@ -54,7 +54,7 @@ class ClientAdmin(admin.ModelAdmin): class SyncGroupAdmin(admin.ModelAdmin): """ Admin page for SyncGroups """ - list_display = ('user', 'num_clients') + list_display = ("user", "num_clients") def num_clients(self, group): """ Numer of clients that belong to this group """ diff --git a/mygpo/users/apps.py b/mygpo/users/apps.py index dad6e1546..bc308bc84 100644 --- a/mygpo/users/apps.py +++ b/mygpo/users/apps.py @@ -5,16 +5,16 @@ def create_missing_profile(sender, **kwargs): """ Creates a UserProfile if a User doesn't have one """ - user = kwargs['instance'] + user = kwargs["instance"] - if not hasattr(user, 'profile'): - UserProfile = apps.get_model('users.UserProfile') + if not hasattr(user, "profile"): + UserProfile = apps.get_model("users.UserProfile") profile = UserProfile.objects.create(user=user) user.profile = profile class UsersConfig(AppConfig): - name = 'mygpo.users' + name = "mygpo.users" verbose_name = "Users and Clients" def ready(self): diff --git a/mygpo/users/backend.py b/mygpo/users/backend.py index 4d11eda59..0a9e67a4c 100644 --- a/mygpo/users/backend.py +++ b/mygpo/users/backend.py @@ -12,7 +12,7 @@ class CaseInsensitiveModelBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): UserModel = get_user_model() users = UserModel.objects.filter(username__iexact=username).order_by( - '-last_login' + "-last_login" ) users = list(users) if len(users) == 0: @@ -22,7 +22,7 @@ def authenticate(self, request, username=None, password=None, **kwargs): return None if len(users) > 1: - logger.error('Login with non-unique username: %s', username) + logger.error("Login with non-unique username: %s", username) user = users[0] if user.check_password(password): diff --git a/mygpo/users/checks.py b/mygpo/users/checks.py index 34ca07593..eabc5658e 100644 --- a/mygpo/users/checks.py +++ b/mygpo/users/checks.py @@ -23,16 +23,14 @@ def check_case_insensitive_users(app_configs=None, **kwargs): usernames = [t[1] for t in non_unique] if len(non_unique) > 0: - txt = 'There are {0} non-unique usernames: {1}'.format( - len(non_unique), ', '.join(usernames[:10] + ['...']) + txt = "There are {0} non-unique usernames: {1}".format( + len(non_unique), ", ".join(usernames[:10] + ["..."]) ) - wid = 'users.W001' + wid = "users.W001" errors.append(Warning(txt, id=wid)) - except (OperationalError, ProgrammingError) as oe: - if 'no such table: auth_user' in str( - oe - ) or 'relation "auth_user" does not exist' in str(oe): + except OperationalError as oe: + if "no such table: auth_user" in str(oe): # Ignore if the table does not yet exist, eg when initally # running ``manage.py migrate`` pass diff --git a/mygpo/users/converters.py b/mygpo/users/converters.py index 3d8bd56a6..3c9917cbc 100644 --- a/mygpo/users/converters.py +++ b/mygpo/users/converters.py @@ -1,5 +1,5 @@ class UsernameConverter: - regex = r'[\w.+-]+' + regex = r"[\w.+-]+" def to_python(self, value): return value @@ -9,7 +9,7 @@ def to_url(self, value): class ClientUIDConverter: - regex = r'[\w.-]+' + regex = r"[\w.-]+" def to_python(self, value): return value diff --git a/mygpo/users/migrations/0001_initial.py b/mygpo/users/migrations/0001_initial.py index 65821c872..38a3a56cf 100644 --- a/mygpo/users/migrations/0001_initial.py +++ b/mygpo/users/migrations/0001_initial.py @@ -7,38 +7,38 @@ class Migration(migrations.Migration): - dependencies = [('auth', '__first__')] + dependencies = [("auth", "__first__")] operations = [ migrations.CreateModel( - name='UserProfile', + name="UserProfile", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('twitter', models.CharField(max_length=15, null=True)), - ('suggestions_up_to_date', models.BooleanField(default=False)), - ('about', models.TextField(blank=True)), - ('google_email', models.CharField(max_length=100, null=True)), - ('subscriptions_token', models.CharField(max_length=32, null=True)), - ('favorite_feeds_token', models.CharField(max_length=32, null=True)), - ('publisher_update_token', models.CharField(max_length=32, null=True)), - ('userpage_token', models.CharField(max_length=32, null=True)), + ("twitter", models.CharField(max_length=15, null=True)), + ("suggestions_up_to_date", models.BooleanField(default=False)), + ("about", models.TextField(blank=True)), + ("google_email", models.CharField(max_length=100, null=True)), + ("subscriptions_token", models.CharField(max_length=32, null=True)), + ("favorite_feeds_token", models.CharField(max_length=32, null=True)), + ("publisher_update_token", models.CharField(max_length=32, null=True)), + ("userpage_token", models.CharField(max_length=32, null=True)), ( - 'user', + "user", models.OneToOneField( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), ), - ('uuid', models.UUIDField(unique=True, max_length=32)), + ("uuid", models.UUIDField(unique=True, max_length=32)), ], - options={'abstract': False}, + options={"abstract": False}, bases=(models.Model,), ) ] diff --git a/mygpo/users/migrations/0002_auto_20140718_1457.py b/mygpo/users/migrations/0002_auto_20140718_1457.py index 3afca7e06..b306c18ad 100644 --- a/mygpo/users/migrations/0002_auto_20140718_1457.py +++ b/mygpo/users/migrations/0002_auto_20140718_1457.py @@ -9,38 +9,38 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('users', '0001_initial'), + ("users", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Client', + name="Client", fields=[ ( - 'id', + "id", models.UUIDField(max_length=32, serialize=False, primary_key=True), ), - ('uid', models.CharField(max_length=64)), - ('name', models.CharField(default='New Device', max_length=100)), + ("uid", models.CharField(max_length=64)), + ("name", models.CharField(default="New Device", max_length=100)), ( - 'type', + "type", models.CharField( - default='other', + default="other", max_length=7, choices=[ - ('desktop', 'Desktop'), - ('laptop', 'Laptop'), - ('mobile', 'Cell phone'), - ('server', 'Server'), - ('tablet', 'Tablet'), - ('other', 'Other'), + ("desktop", "Desktop"), + ("laptop", "Laptop"), + ("mobile", "Cell phone"), + ("server", "Server"), + ("tablet", "Tablet"), + ("other", "Other"), ], ), ), - ('deleted', models.BooleanField(default=False)), - ('user_agent', models.CharField(max_length=300)), + ("deleted", models.BooleanField(default=False)), + ("user_agent", models.CharField(max_length=300)), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -50,6 +50,6 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='client', unique_together=set([('user', 'uid')]) + name="client", unique_together=set([("user", "uid")]) ), ] diff --git a/mygpo/users/migrations/0003_auto_20140718_1502.py b/mygpo/users/migrations/0003_auto_20140718_1502.py index 73d9b1095..7f98d7549 100644 --- a/mygpo/users/migrations/0003_auto_20140718_1502.py +++ b/mygpo/users/migrations/0003_auto_20140718_1502.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('users', '0002_auto_20140718_1457')] + dependencies = [("users", "0002_auto_20140718_1457")] operations = [ migrations.AlterField( - model_name='client', - name='user_agent', + model_name="client", + name="user_agent", field=models.CharField(max_length=300, null=True, blank=True), ) ] diff --git a/mygpo/users/migrations/0004_auto_20140718_1655.py b/mygpo/users/migrations/0004_auto_20140718_1655.py index abf4544ca..3e69dfe6b 100644 --- a/mygpo/users/migrations/0004_auto_20140718_1655.py +++ b/mygpo/users/migrations/0004_auto_20140718_1655.py @@ -9,24 +9,24 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('users', '0003_auto_20140718_1502'), + ("users", "0003_auto_20140718_1502"), ] operations = [ migrations.CreateModel( - name='SyncGroup', + name="SyncGroup", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -36,23 +36,23 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AddField( - model_name='client', - name='sync_group', + model_name="client", + name="sync_group", field=models.ForeignKey( - to='users.SyncGroup', null=True, on_delete=models.CASCADE + to="users.SyncGroup", null=True, on_delete=models.CASCADE ), preserve_default=True, ), migrations.AddField( - model_name='userprofile', - name='activation_key', + model_name="userprofile", + name="activation_key", field=models.CharField(max_length=32, null=True), preserve_default=True, ), migrations.AddField( - model_name='userprofile', - name='settings', - field=models.TextField(default='{}'), + model_name="userprofile", + name="settings", + field=models.TextField(default="{}"), preserve_default=True, ), ] diff --git a/mygpo/users/migrations/0005_auto_20140719_1105.py b/mygpo/users/migrations/0005_auto_20140719_1105.py index e20401ec8..94a7c058d 100644 --- a/mygpo/users/migrations/0005_auto_20140719_1105.py +++ b/mygpo/users/migrations/0005_auto_20140719_1105.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): - dependencies = [('users', '0004_auto_20140718_1655')] + dependencies = [("users", "0004_auto_20140718_1655")] operations = [ migrations.AlterField( - model_name='userprofile', - name='activation_key', + model_name="userprofile", + name="activation_key", field=models.CharField(max_length=40, null=True), ) ] diff --git a/mygpo/users/migrations/0006_auto_20140726_0928.py b/mygpo/users/migrations/0006_auto_20140726_0928.py index 5b617ac29..d9b1e68c5 100644 --- a/mygpo/users/migrations/0006_auto_20140726_0928.py +++ b/mygpo/users/migrations/0006_auto_20140726_0928.py @@ -8,43 +8,43 @@ class Migration(migrations.Migration): - dependencies = [('auth', '__first__'), ('users', '0005_auto_20140719_1105')] + dependencies = [("auth", "__first__"), ("users", "0005_auto_20140719_1105")] operations = [ migrations.CreateModel( - name='UserProxy', fields=[], options={'proxy': True}, bases=('auth.user',) + name="UserProxy", fields=[], options={"proxy": True}, bases=("auth.user",) ), migrations.AlterField( - model_name='client', - name='uid', + model_name="client", + name="uid", field=models.CharField( max_length=64, validators=[mygpo.users.models.UIDValidator()] ), ), migrations.AlterField( - model_name='userprofile', - name='favorite_feeds_token', + model_name="userprofile", + name="favorite_feeds_token", field=models.CharField( default=mygpo.utils.random_token, max_length=32, null=True ), ), migrations.AlterField( - model_name='userprofile', - name='publisher_update_token', + model_name="userprofile", + name="publisher_update_token", field=models.CharField( default=mygpo.utils.random_token, max_length=32, null=True ), ), migrations.AlterField( - model_name='userprofile', - name='subscriptions_token', + model_name="userprofile", + name="subscriptions_token", field=models.CharField( default=mygpo.utils.random_token, max_length=32, null=True ), ), migrations.AlterField( - model_name='userprofile', - name='userpage_token', + model_name="userprofile", + name="userpage_token", field=models.CharField( default=mygpo.utils.random_token, max_length=32, null=True ), diff --git a/mygpo/users/migrations/0007_syncgroup_protect.py b/mygpo/users/migrations/0007_syncgroup_protect.py index 95598c0ff..5a680eec5 100644 --- a/mygpo/users/migrations/0007_syncgroup_protect.py +++ b/mygpo/users/migrations/0007_syncgroup_protect.py @@ -7,14 +7,14 @@ class Migration(migrations.Migration): - dependencies = [('users', '0006_auto_20140726_0928')] + dependencies = [("users", "0006_auto_20140726_0928")] operations = [ migrations.AlterField( - model_name='client', - name='sync_group', + model_name="client", + name="sync_group", field=models.ForeignKey( - to='users.SyncGroup', + to="users.SyncGroup", on_delete=django.db.models.deletion.PROTECT, null=True, ), diff --git a/mygpo/users/migrations/0008_user_index_email.py b/mygpo/users/migrations/0008_user_index_email.py index 501ef4373..cb96c552a 100644 --- a/mygpo/users/migrations/0008_user_index_email.py +++ b/mygpo/users/migrations/0008_user_index_email.py @@ -7,17 +7,17 @@ class Migration(migrations.Migration): - dependencies = [('users', '0007_syncgroup_protect'), ('auth', '0001_initial')] + dependencies = [("users", "0007_syncgroup_protect"), ("auth", "0001_initial")] operations = [ migrations.RunSQL( sql=[ ( - 'CREATE INDEX django_auth_user_email ' - 'ON auth_user (email, username);', + "CREATE INDEX django_auth_user_email " + "ON auth_user (email, username);", None, ) ], - reverse_sql=[('DROP INDEX IF EXISTS django_auth_user_email;', None)], + reverse_sql=[("DROP INDEX IF EXISTS django_auth_user_email;", None)], ) ] diff --git a/mygpo/users/migrations/0009_user_unique_email.py b/mygpo/users/migrations/0009_user_unique_email.py index 200fe9d97..0c5b7be05 100644 --- a/mygpo/users/migrations/0009_user_unique_email.py +++ b/mygpo/users/migrations/0009_user_unique_email.py @@ -7,17 +7,17 @@ class Migration(migrations.Migration): - dependencies = [('users', '0008_user_index_email')] + dependencies = [("users", "0008_user_index_email")] operations = [ migrations.RunSQL( sql=[ ( - 'CREATE UNIQUE INDEX django_auth_unique_email ' - 'ON auth_user (email);', + "CREATE UNIQUE INDEX django_auth_unique_email " + "ON auth_user (email);", None, ) ], - reverse_sql=[('DROP INDEX IF EXISTS django_auth_unique_email;', None)], + reverse_sql=[("DROP INDEX IF EXISTS django_auth_unique_email;", None)], ) ] diff --git a/mygpo/users/migrations/0010_user_profile_related.py b/mygpo/users/migrations/0010_user_profile_related.py index 6428ce334..1e45255d1 100644 --- a/mygpo/users/migrations/0010_user_profile_related.py +++ b/mygpo/users/migrations/0010_user_profile_related.py @@ -7,14 +7,14 @@ class Migration(migrations.Migration): - dependencies = [('users', '0009_user_unique_email')] + dependencies = [("users", "0009_user_unique_email")] operations = [ migrations.AlterField( - model_name='userprofile', - name='user', + model_name="userprofile", + name="user", field=models.OneToOneField( - related_name='profile', + related_name="profile", to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ), diff --git a/mygpo/users/migrations/0011_syncgroup_blank.py b/mygpo/users/migrations/0011_syncgroup_blank.py index 88e4e5d82..fe98b6430 100644 --- a/mygpo/users/migrations/0011_syncgroup_blank.py +++ b/mygpo/users/migrations/0011_syncgroup_blank.py @@ -7,16 +7,16 @@ class Migration(migrations.Migration): - dependencies = [('users', '0010_user_profile_related')] + dependencies = [("users", "0010_user_profile_related")] operations = [ migrations.AlterField( - model_name='client', - name='sync_group', + model_name="client", + name="sync_group", field=models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, blank=True, - to='users.SyncGroup', + to="users.SyncGroup", null=True, ), ) diff --git a/mygpo/users/migrations/0012_remove_userprofile_settings.py b/mygpo/users/migrations/0012_remove_userprofile_settings.py index 6a5b16d6e..5b581275c 100644 --- a/mygpo/users/migrations/0012_remove_userprofile_settings.py +++ b/mygpo/users/migrations/0012_remove_userprofile_settings.py @@ -7,8 +7,8 @@ class Migration(migrations.Migration): dependencies = [ - ('users', '0011_syncgroup_blank'), - ('usersettings', '0002_move_existing'), + ("users", "0011_syncgroup_blank"), + ("usersettings", "0002_move_existing"), ] - operations = [migrations.RemoveField(model_name='userprofile', name='settings')] + operations = [migrations.RemoveField(model_name="userprofile", name="settings")] diff --git a/mygpo/users/migrations/0013_remove_userprofile_uuid.py b/mygpo/users/migrations/0013_remove_userprofile_uuid.py index 0d391e340..ff0a4e290 100644 --- a/mygpo/users/migrations/0013_remove_userprofile_uuid.py +++ b/mygpo/users/migrations/0013_remove_userprofile_uuid.py @@ -4,6 +4,6 @@ class Migration(migrations.Migration): - dependencies = [('users', '0012_remove_userprofile_settings')] + dependencies = [("users", "0012_remove_userprofile_settings")] - operations = [migrations.RemoveField(model_name='userprofile', name='uuid')] + operations = [migrations.RemoveField(model_name="userprofile", name="uuid")] diff --git a/mygpo/users/migrations/0014_django_uuidfield.py b/mygpo/users/migrations/0014_django_uuidfield.py index a41c8657e..fdabddbd0 100644 --- a/mygpo/users/migrations/0014_django_uuidfield.py +++ b/mygpo/users/migrations/0014_django_uuidfield.py @@ -4,12 +4,12 @@ class Migration(migrations.Migration): - dependencies = [('users', '0013_remove_userprofile_uuid')] + dependencies = [("users", "0013_remove_userprofile_uuid")] operations = [ migrations.AlterField( - model_name='client', - name='id', + model_name="client", + name="id", field=models.UUIDField(serialize=False, primary_key=True), ) ] diff --git a/mygpo/users/migrations/0015_case_insensitive_username.py b/mygpo/users/migrations/0015_case_insensitive_username.py index 7ee7702ef..ec1949cc4 100644 --- a/mygpo/users/migrations/0015_case_insensitive_username.py +++ b/mygpo/users/migrations/0015_case_insensitive_username.py @@ -6,14 +6,14 @@ def forward(apps, schema_editor): # This index can apparently not be created on sqlite # As it is not recommended for production use, we can just # skip the index there - if schema_editor.connection.vendor == 'sqlite': + if schema_editor.connection.vendor == "sqlite": return migrations.RunSQL( sql=[ ( - 'CREATE UNIQUE INDEX user_case_insensitive_unique ' - 'ON auth_user ((lower(username)));', + "CREATE UNIQUE INDEX user_case_insensitive_unique " + "ON auth_user ((lower(username)));", None, ) ] @@ -21,13 +21,13 @@ def forward(apps, schema_editor): def reverse(apps, schema_editor): - migrations.RunSQL([('DROP INDEX IF EXISTS user_case_insensitive_unique', None)]) + migrations.RunSQL([("DROP INDEX IF EXISTS user_case_insensitive_unique", None)]) class Migration(migrations.Migration): """ Create a unique case-insensitive index on the username column """ - dependencies = [('auth', '0001_initial'), ('users', '0014_django_uuidfield')] + dependencies = [("auth", "0001_initial"), ("users", "0014_django_uuidfield")] operations = [ # Wrap RunSQL in RunPython to check for DB backend diff --git a/mygpo/users/models.py b/mygpo/users/models.py index 4f71caf95..4defeaf87 100644 --- a/mygpo/users/models.py +++ b/mygpo/users/models.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) -RE_DEVICE_UID = re.compile(r'^[\w.-]+$') +RE_DEVICE_UID = re.compile(r"^[\w.-]+$") # TODO: derive from ValidationException? @@ -32,15 +32,15 @@ class SubscriptionException(Exception): """ raised when a subscription can not be modified """ -GroupedDevices = collections.namedtuple('GroupedDevices', 'is_synced devices') +GroupedDevices = collections.namedtuple("GroupedDevices", "is_synced devices") class UIDValidator(RegexValidator): """ Validates that the Device UID conforms to the given regex """ regex = RE_DEVICE_UID - message = 'Invalid Device ID' - code = 'invalid-uid' + message = "Invalid Device ID" + code = "invalid-uid" class UserProxyQuerySet(models.QuerySet): @@ -94,7 +94,7 @@ def get_grouped_devices(self): """ Returns groups of synced devices and a unsynced group """ clients = Client.objects.filter(user=self, deleted=False).order_by( - '-sync_group' + "-sync_group" ) last_group = object() @@ -121,7 +121,7 @@ class UserProfile(TwitterModel): # the user to which this profile belongs user = models.OneToOneField( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile' + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile" ) # if False, suggestions should be updated @@ -158,7 +158,7 @@ def get_token(self, token_name): """ returns a token """ if token_name not in TOKEN_NAMES: - raise TokenException('Invalid token name %s' % token_name) + raise TokenException("Invalid token name %s" % token_name) return getattr(self, token_name) @@ -166,7 +166,7 @@ def create_new_token(self, token_name): """ resets a token """ if token_name not in TOKEN_NAMES: - raise TokenException('Invalid token name %s' % token_name) + raise TokenException("Invalid token name %s" % token_name) setattr(self, token_name, random_token()) @@ -194,7 +194,7 @@ def sync(self): for client in self.client_set.all(): missing_podcasts = self.get_missing_podcasts(client, podcasts) for podcast in missing_podcasts: - subscribe.delay(podcast, self.user, client) + subscribe.delay(podcast.pk, self.user.pk, client.uid) def get_subscribed_podcasts(self): return Podcast.objects.filter(subscription__client__sync_group=self) @@ -207,26 +207,26 @@ def get_missing_podcasts(self, client, all_podcasts): @property def display_name(self): clients = self.client_set.all() - return ', '.join(client.display_name for client in clients) + return ", ".join(client.display_name for client in clients) class Client(UUIDModel, DeleteableModel): """ A client application """ - DESKTOP = 'desktop' - LAPTOP = 'laptop' - MOBILE = 'mobile' - SERVER = 'server' - TABLET = 'tablet' - OTHER = 'other' + DESKTOP = "desktop" + LAPTOP = "laptop" + MOBILE = "mobile" + SERVER = "server" + TABLET = "tablet" + OTHER = "other" TYPES = ( - (DESKTOP, _('Desktop')), - (LAPTOP, _('Laptop')), - (MOBILE, _('Cell phone')), - (SERVER, _('Server')), - (TABLET, _('Tablet')), - (OTHER, _('Other')), + (DESKTOP, _("Desktop")), + (LAPTOP, _("Laptop")), + (MOBILE, _("Cell phone")), + (SERVER, _("Server")), + (TABLET, _("Tablet")), + (OTHER, _("Other")), ) # User-assigned ID; must be unique for the user @@ -236,7 +236,7 @@ class Client(UUIDModel, DeleteableModel): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # User-assigned name - name = models.CharField(max_length=100, default='New Device') + name = models.CharField(max_length=100, default="New Device") # one of several predefined types type = models.CharField( @@ -251,14 +251,14 @@ class Client(UUIDModel, DeleteableModel): ) class Meta: - unique_together = (('user', 'uid'),) + unique_together = (("user", "uid"),) @transaction.atomic def sync_with(self, other): """ Puts two devices in a common sync group""" if self.user != other.user: - raise ValueError('the devices do not belong to the user') + raise ValueError("the devices do not belong to the user") if ( self.sync_group is not None @@ -289,15 +289,15 @@ def stop_sync(self): """ Stop synchronisation with other clients """ sg = self.sync_group - logger.info('Stopping synchronisation of %r', self) + logger.info("Stopping synchronisation of %r", self) self.sync_group = None self.save() clients = Client.objects.filter(sync_group=sg) - logger.info('%d other clients remaining in sync group', len(clients)) + logger.info("%d other clients remaining in sync group", len(clients)) if len(clients) < 2: - logger.info('Deleting sync group %r', sg) + logger.info("Deleting sync group %r", sg) for client in clients: client.sync_group = None client.save() @@ -305,9 +305,9 @@ def stop_sync(self): sg.delete() def get_sync_targets(self): - """ Returns the devices and groups with which the device can be synced + """Returns the devices and groups with which the device can be synced - Groups are represented as lists of devices """ + Groups are represented as lists of devices""" user = UserProxy.objects.from_user(self.user) for group in user.get_grouped_devices(): @@ -326,10 +326,10 @@ def get_sync_targets(self): yield dev def get_subscribed_podcasts(self): - """ Returns all subscribed podcasts for the device + """Returns all subscribed podcasts for the device The attribute "url" contains the URL that was used when subscribing to - the podcast """ + the podcast""" return Podcast.objects.filter(subscription__client=self) def synced_with(self): @@ -343,14 +343,14 @@ def display_name(self): return self.name or self.uid def __str__(self): - return '{name} ({uid})'.format(name=self.name, uid=self.uid) + return "{name} ({uid})".format(name=self.name, uid=self.uid) TOKEN_NAMES = ( - 'subscriptions_token', - 'favorite_feeds_token', - 'publisher_update_token', - 'userpage_token', + "subscriptions_token", + "favorite_feeds_token", + "publisher_update_token", + "userpage_token", ) @@ -366,8 +366,8 @@ def from_action_dict(cls, action): entry = HistoryEntry() - if 'timestamp' in action: - ts = action.pop('timestamp') + if "timestamp" in action: + ts = action.pop("timestamp") entry.timestamp = dateutil.parser.parse(ts) for key, value in action.items(): @@ -377,7 +377,7 @@ def from_action_dict(cls, action): @property def playmark(self): - return getattr(self, 'position', None) + return getattr(self, "position", None) @classmethod def fetch_data(cls, user, entries, podcasts=None, episodes=None): @@ -385,41 +385,41 @@ def fetch_data(cls, user, entries, podcasts=None, episodes=None): if podcasts is None: # load podcast data - podcast_ids = [getattr(x, 'podcast_id', None) for x in entries] + podcast_ids = [getattr(x, "podcast_id", None) for x in entries] podcast_ids = filter(None, podcast_ids) podcasts = Podcast.objects.filter(id__in=podcast_ids).prefetch_related( - 'slugs' + "slugs" ) podcasts = {podcast.id.hex: podcast for podcast in podcasts} if episodes is None: # load episode data - episode_ids = [getattr(x, 'episode_id', None) for x in entries] + episode_ids = [getattr(x, "episode_id", None) for x in entries] episode_ids = filter(None, episode_ids) episodes = ( Episode.objects.filter(id__in=episode_ids) - .select_related('podcast') - .prefetch_related('slugs', 'podcast__slugs') + .select_related("podcast") + .prefetch_related("slugs", "podcast__slugs") ) episodes = {episode.id.hex: episode for episode in episodes} # load device data # does not need pre-populated data because no db-access is required - device_ids = [getattr(x, 'device_id', None) for x in entries] + device_ids = [getattr(x, "device_id", None) for x in entries] device_ids = filter(None, device_ids) devices = {client.id.hex: client for client in user.client_set.all()} for entry in entries: - podcast_id = getattr(entry, 'podcast_id', None) + podcast_id = getattr(entry, "podcast_id", None) entry.podcast = podcasts.get(podcast_id, None) - episode_id = getattr(entry, 'episode_id', None) + episode_id = getattr(entry, "episode_id", None) entry.episode = episodes.get(episode_id, None) - if hasattr(entry, 'user'): + if hasattr(entry, "user"): entry.user = user - device = devices.get(getattr(entry, 'device_id', None), None) + device = devices.get(getattr(entry, "device_id", None), None) entry.device = device return entries diff --git a/mygpo/users/settings.py b/mygpo/users/settings.py index 6c78004b7..006673ed4 100644 --- a/mygpo/users/settings.py +++ b/mygpo/users/settings.py @@ -1,20 +1,20 @@ from collections import namedtuple -WellKnownSetting = namedtuple('WellKnownSetting', 'name default') +WellKnownSetting = namedtuple("WellKnownSetting", "name default") ## Well-known settings # this should be documented at # https://gpoddernet.readthedocs.io/en/latest/api//Settings#Known_Settings # Flag to allow storing of user-agents -STORE_UA = WellKnownSetting('store_user_agent', True) +STORE_UA = WellKnownSetting("store_user_agent", True) # Flag to mark a subscription as public -PUBLIC_SUB_PODCAST = WellKnownSetting('public_subscription', True) +PUBLIC_SUB_PODCAST = WellKnownSetting("public_subscription", True) # Default public-flag value (stored in the podcast) -PUBLIC_SUB_USER = WellKnownSetting('public_subscriptions', True) +PUBLIC_SUB_USER = WellKnownSetting("public_subscriptions", True) # Flag to mark an episode as favorite -FAV_FLAG = WellKnownSetting('is_favorite', False) +FAV_FLAG = WellKnownSetting("is_favorite", False) diff --git a/mygpo/users/subscriptions.py b/mygpo/users/subscriptions.py index 5a211f6da..ac6ebcf9c 100644 --- a/mygpo/users/subscriptions.py +++ b/mygpo/users/subscriptions.py @@ -29,11 +29,11 @@ def __iter__(self): class PodcastPercentageListenedSorter(PodcastSorter): - """ Sorts podcasts by the percentage of listened episodes + """Sorts podcasts by the percentage of listened episodes Adds the attributes percent_listened and episodes_listened to the podcasts - Cost: 1 DB query """ + Cost: 1 DB query""" def __init__(self, podcasts, user): super(PodcastPercentageListenedSorter, self).__init__(podcasts) @@ -62,9 +62,9 @@ def subscription_changes(device_id, podcast_states, since, until): add, rem = [], [] for p_state in podcast_states: change = p_state.get_change_between(device_id, since, until) - if change == 'subscribe': + if change == "subscribe": add.append(p_state.ref_url) - elif change == 'unsubscribe': + elif change == "unsubscribe": rem.append(p_state.ref_url) return add, rem diff --git a/mygpo/users/tasks.py b/mygpo/users/tasks.py index ae159aa74..00d8b738e 100644 --- a/mygpo/users/tasks.py +++ b/mygpo/users/tasks.py @@ -35,7 +35,7 @@ def sync_user(user_pk): pass except Exception as e: - logger.exception('retrying task') + logger.exception("retrying task") raise sync_user.retry() @@ -48,7 +48,7 @@ def remove_inactive_users(): # time for which to keep unactivated and deleted users valid_days = settings.ACTIVATION_VALID_DAYS remove_before = datetime.utcnow() - timedelta(days=valid_days) - logger.warning('Removing unactivated users before %s', remove_before) + logger.warning("Removing unactivated users before %s", remove_before) users = User.objects.filter(is_active=False, date_joined__lt=remove_before) @@ -63,12 +63,12 @@ def remove_inactive_users(): @periodic_task(run_every=timedelta(hours=1)) @close_connection def clearsessions(): - """ Clear expired sessions + """Clear expired sessions This runs code that should normally be run by ``manage.py clearsessions``. If Django's internals change, see django/contrib/sessions/management/commands/clearsessions.py for the - current implementation. """ + current implementation.""" engine = import_module(settings.SESSION_ENGINE) engine.SessionStore.clear_expired() diff --git a/mygpo/users/tests.py b/mygpo/users/tests.py index 6e844372d..2c4dd52c1 100644 --- a/mygpo/users/tests.py +++ b/mygpo/users/tests.py @@ -18,21 +18,21 @@ class DeviceSyncTests(unittest.TestCase): def setUp(self): - self.user = UserProxy(username='test') - self.user.email = 'test@invalid.com' - self.user.set_password('secret!') + self.user = UserProxy(username="test") + self.user.email = "test@invalid.com" + self.user.set_password("secret!") self.user.save() def test_group(self): - dev1 = Client.objects.create(id=uuid.uuid1(), user=self.user, uid='d1') - dev2 = Client.objects.create(id=uuid.uuid1(), user=self.user, uid='d2') + dev1 = Client.objects.create(id=uuid.uuid1(), user=self.user, uid="d1") + dev2 = Client.objects.create(id=uuid.uuid1(), user=self.user, uid="d2") group = next(self.user.get_grouped_devices()) self.assertEqual(group.is_synced, False) self.assertIn(dev1, group.devices) self.assertIn(dev2, group.devices) - dev3 = Client.objects.create(id=uuid.uuid1(), user=self.user, uid='d3') + dev3 = Client.objects.create(id=uuid.uuid1(), user=self.user, uid="d3") dev1.sync_with(dev3) @@ -60,25 +60,25 @@ def tearDown(self): class UnsubscribeMergeTests(TestCase): """ Test if merged podcasts can be properly unsubscribed """ - P2_URL = 'http://test.org/podcast/' + P2_URL = "http://test.org/podcast/" def setUp(self): self.podcast1 = Podcast.objects.get_or_create_for_url( - 'http://example.com/feed.rss' + "http://example.com/feed.rss" ).object self.podcast2 = Podcast.objects.get_or_create_for_url(self.P2_URL).object User = get_user_model() - self.user = User(username='test-merge') - self.user.email = 'test@example.com' - self.user.set_password('secret!') + self.user = User(username="test-merge") + self.user.email = "test@example.com" + self.user.set_password("secret!") self.user.save() - self.device = get_device(self.user, 'dev', '') + self.device = get_device(self.user, "dev", "") def test_merge_podcasts(self): - subscribe(self.podcast2, self.user, self.device) + subscribe(self.podcast2.pk, self.user.pk, self.device.uid) # merge podcast2 into podcast1 pm = PodcastMerger([self.podcast1, self.podcast2], Counter(), []) @@ -86,7 +86,7 @@ def test_merge_podcasts(self): # get podcast for URL of podcast2 and unsubscribe from it p = Podcast.objects.get(urls__url=self.P2_URL) - unsubscribe(p, self.user, self.device) + unsubscribe(p.pk, self.user.pk, self.device.uid) subscriptions = Podcast.objects.filter(subscription__user=self.user) self.assertEqual(0, len(subscriptions)) @@ -100,14 +100,14 @@ class AuthTests(TestCase): def setUp(self): self.user, pwd = create_user() self.client = TClient() - wrong_pwd = pwd + '1234' + wrong_pwd = pwd + "1234" self.extra = { - 'HTTP_AUTHORIZATION': create_auth_string(self.user.username, wrong_pwd) + "HTTP_AUTHORIZATION": create_auth_string(self.user.username, wrong_pwd) } def test_queries_failed_auth(self): """ Verifies the number of queries that are executed on failed auth """ - url = reverse('api-all-subscriptions', args=(self.user.username, 'opml')) + url = reverse("api-all-subscriptions", args=(self.user.username, "opml")) with self.assertNumQueries(1): resp = self.client.get(url, **self.extra) self.assertEqual(resp.status_code, 401, resp.content) diff --git a/mygpo/users/urls.py b/mygpo/users/urls.py index c87b2db29..0b74cef51 100644 --- a/mygpo/users/urls.py +++ b/mygpo/users/urls.py @@ -5,91 +5,91 @@ from .views import registration, settings, device, user from mygpo.users import converters -register_converter(converters.ClientUIDConverter, 'client-uid') +register_converter(converters.ClientUIDConverter, "client-uid") urlpatterns = [ - path('register/', registration.RegistrationView.as_view(), name='register'), + path("register/", registration.RegistrationView.as_view(), name="register"), path( - 'registration_complete/', + "registration_complete/", registration.TemplateView.as_view( - template_name='registration/registration_complete.html' + template_name="registration/registration_complete.html" ), - name='registration-complete', + name="registration-complete", ), - path('activate/', registration.ActivationView.as_view()), + path("activate/", registration.ActivationView.as_view()), path( - 'registration/resend', + "registration/resend", registration.ResendActivationView.as_view(), - name='resend-activation', + name="resend-activation", ), path( - 'registration/resent', + "registration/resent", registration.ResentActivationView.as_view(), - name='resent-activation', + name="resent-activation", ), - path('account/', settings.account, name='account'), - path('account/privacy', settings.privacy, name='privacy'), - path('account/profile', settings.ProfileView.as_view(), name='profile'), + path("account/", settings.account, name="account"), + path("account/privacy", settings.privacy, name="privacy"), + path("account/profile", settings.ProfileView.as_view(), name="profile"), path( - 'account/google/remove', + "account/google/remove", settings.AccountRemoveGoogle.as_view(), - name='account-google-remove', + name="account-google-remove", ), path( - 'account/privacy/default-public', + "account/privacy/default-public", settings.DefaultPrivacySettings.as_view(public=True), - name='privacy_default_public', + name="privacy_default_public", ), path( - 'account/privacy/default-private', + "account/privacy/default-private", settings.DefaultPrivacySettings.as_view(public=False), - name='privacy_default_private', + name="privacy_default_private", ), path( - 'account/privacy//public', + "account/privacy//public", settings.PodcastPrivacySettings.as_view(public=True), - name='privacy_podcast_public', + name="privacy_podcast_public", ), path( - 'account/privacy//private', + "account/privacy//private", settings.PodcastPrivacySettings.as_view(public=False), - name='privacy_podcast_private', + name="privacy_podcast_private", ), - path('account/delete', settings.delete_account, name='delete-account'), - path('devices/', device.overview, name='devices'), - path('devices/create-device', device.create, name='device-create'), - path('device/.opml', device.opml, name='device-opml'), - path('device/', device.show, name='device'), + path("account/delete", settings.delete_account, name="delete-account"), + path("devices/", device.overview, name="devices"), + path("devices/create-device", device.create, name="device-create"), + path("device/.opml", device.opml, name="device-opml"), + path("device/", device.show, name="device"), path( - 'device//symbian.opml', + "device//symbian.opml", device.symbian_opml, - name='device-symbian-opml', + name="device-symbian-opml", ), - path('device//sync', device.sync, name='device-sync'), - path('device//unsync', device.unsync, name='device-unsync'), - path('device//resync', device.resync, name='trigger-sync'), - path('device//delete', device.delete, name='device-delete'), + path("device//sync", device.sync, name="device-sync"), + path("device//unsync", device.unsync, name="device-unsync"), + path("device//resync", device.resync, name="trigger-sync"), + path("device//delete", device.delete, name="device-delete"), path( - 'device//remove', + "device//remove", device.delete_permanently, - name='device-delete-permanently', + name="device-delete-permanently", ), - path('device//undelete', device.undelete, name='device-undelete'), - path('device//edit', device.edit, name='device-edit'), - path('device//update', device.update, name='device-update'), + path("device//undelete", device.undelete, name="device-undelete"), + path("device//edit", device.edit, name="device-edit"), + path("device//update", device.update, name="device-update"), path( - 'device//upload-opml', + "device//upload-opml", device.upload_opml, - name='device-upload-opml', + name="device-upload-opml", ), - path('register/restore_password', user.restore_password, name='restore-password'), - path('login/', user.LoginView.as_view(), name='login'), - path('login/google', user.GoogleLogin.as_view(), name='login-google'), + path("register/restore_password", user.restore_password, name="restore-password"), + path("login/", user.LoginView.as_view(), name="login"), + path("login/google", user.GoogleLogin.as_view(), name="login-google"), path( - 'login/oauth2callback', + "login/oauth2callback", user.GoogleLoginCallback.as_view(), - name='login-google-callback', + name="login-google-callback", ), - path('logout/', LogoutView.as_view(), kwargs={'next_page': '/'}, name='logout'), + path("logout/", LogoutView.as_view(), kwargs={"next_page": "/"}, name="logout"), ] diff --git a/mygpo/users/views/device.py b/mygpo/users/views/device.py index 86c929d14..390fa4569 100644 --- a/mygpo/users/views/device.py +++ b/mygpo/users/views/device.py @@ -38,16 +38,16 @@ def overview(request): # create a "default" device device = Client() device_form = DeviceForm( - {'name': device.name, 'type': device.type, 'uid': device.uid} + {"name": device.name, "type": device.type, "uid": device.uid} ) return render( request, - 'devicelist.html', + "devicelist.html", { - 'device_groups': list(device_groups), - 'deleted_devices': list(deleted_devices), - 'device_form': device_form, + "device_groups": list(device_groups), + "deleted_devices": list(deleted_devices), + "device_form": device_form, }, ) @@ -79,58 +79,58 @@ def show(request, device): sync_targets = list(device.get_sync_targets()) sync_form = SyncForm() - sync_form.set_targets(sync_targets, _('Synchronize with the following devices')) + sync_form.set_targets(sync_targets, _("Synchronize with the following devices")) return render( request, - 'device.html', + "device.html", { - 'device': device, - 'sync_form': sync_form, - 'subscriptions': subscriptions, - 'synced_with': synced_with, - 'has_sync_targets': len(sync_targets) > 0, + "device": device, + "sync_form": sync_form, + "subscriptions": subscriptions, + "synced_with": synced_with, + "has_sync_targets": len(sync_targets) > 0, }, ) @login_required @never_cache -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def create(request): device_form = DeviceForm(request.POST) if not device_form.is_valid(): - messages.error(request, _('Please fill out all fields.')) - return HttpResponseRedirect(reverse('devices')) + messages.error(request, _("Please fill out all fields.")) + return HttpResponseRedirect(reverse("devices")) try: device = Client() device.user = request.user device.id = uuid.uuid1() - device.name = device_form.cleaned_data['name'] - device.type = device_form.cleaned_data['type'] - device.uid = device_form.cleaned_data['uid'].replace(' ', '-') + device.name = device_form.cleaned_data["name"] + device.type = device_form.cleaned_data["type"] + device.uid = device_form.cleaned_data["uid"].replace(" ", "-") device.full_clean() device.save() - messages.success(request, _('Device saved')) + messages.success(request, _("Device saved")) except ValidationError as e: - messages.error(request, '; '.join(e.messages)) - return HttpResponseRedirect(reverse('devices')) + messages.error(request, "; ".join(e.messages)) + return HttpResponseRedirect(reverse("devices")) except IntegrityError: messages.error( request, _("You can't use the same Device " "ID for two devices.") ) - return HttpResponseRedirect(reverse('devices')) + return HttpResponseRedirect(reverse("devices")) - return HttpResponseRedirect(reverse('device-edit', args=[device.uid])) + return HttpResponseRedirect(reverse("device-edit", args=[device.uid])) @device_decorator @login_required -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def update(request, device): device_form = DeviceForm(request.POST) @@ -139,12 +139,12 @@ def update(request, device): if device_form.is_valid(): try: - device.name = device_form.cleaned_data['name'] - device.type = device_form.cleaned_data['type'] - device.uid = device_form.cleaned_data['uid'].replace(' ', '-') + device.name = device_form.cleaned_data["name"] + device.type = device_form.cleaned_data["type"] + device.uid = device_form.cleaned_data["uid"].replace(" ", "-") device.full_clean() device.save() - messages.success(request, _('Device updated')) + messages.success(request, _("Device updated")) uid = device.uid # accept the new UID after rest has succeeded except ValidationError as e: @@ -155,33 +155,33 @@ def update(request, device): request, _("You can't use the same Device " "ID for two devices.") ) - return HttpResponseRedirect(reverse('device-edit', args=[uid])) + return HttpResponseRedirect(reverse("device-edit", args=[uid])) @device_decorator @login_required -@allowed_methods(['GET']) +@allowed_methods(["GET"]) def edit(request, device): device_form = DeviceForm( - {'name': device.name, 'type': device.type, 'uid': device.uid} + {"name": device.name, "type": device.type, "uid": device.uid} ) synced_with = device.synced_with() sync_targets = list(device.get_sync_targets()) sync_form = SyncForm() - sync_form.set_targets(sync_targets, _('Synchronize with the following devices')) + sync_form.set_targets(sync_targets, _("Synchronize with the following devices")) return render( request, - 'device-edit.html', + "device-edit.html", { - 'device': device, - 'device_form': device_form, - 'sync_form': sync_form, - 'synced_with': synced_with, - 'has_sync_targets': len(sync_targets) > 0, + "device": device, + "device_form": device_form, + "sync_form": sync_form, + "synced_with": synced_with, + "has_sync_targets": len(sync_targets) > 0, }, ) @@ -190,20 +190,20 @@ def edit(request, device): @login_required def upload_opml(request, device): - if not 'opml' in request.FILES: - return HttpResponseRedirect(reverse('device-edit', args=[device.uid])) + if not "opml" in request.FILES: + return HttpResponseRedirect(reverse("device-edit", args=[device.uid])) try: - opml = request.FILES['opml'].read().decode('utf-8') - subscriptions = simple.parse_subscription(opml, 'opml') + opml = request.FILES["opml"].read().decode("utf-8") + subscriptions = simple.parse_subscription(opml, "opml") simple.set_subscriptions(subscriptions, request.user, device.uid, None) except (ValueError, ExpatError, UnicodeDecodeError) as ex: - msg = _('Could not upload subscriptions: {err}').format(err=str(ex)) + msg = _("Could not upload subscriptions: {err}").format(err=str(ex)) messages.error(request, msg) - return HttpResponseRedirect(reverse('device-edit', args=[device.uid])) + return HttpResponseRedirect(reverse("device-edit", args=[device.uid])) - return HttpResponseRedirect(reverse('device', args=[device.uid])) + return HttpResponseRedirect(reverse("device", args=[device.uid])) @device_decorator @@ -211,10 +211,10 @@ def upload_opml(request, device): def opml(request, device): response = simple.format_podcast_list( simple.get_subscriptions(request.user, device.uid), - 'opml', + "opml", request.user.username, ) - response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid + response["Content-Disposition"] = "attachment; filename=%s.opml" % device.uid return response @@ -224,14 +224,14 @@ def symbian_opml(request, device): subscriptions = simple.get_subscriptions(request.user, device.uid) subscriptions = map(symbian_opml_changes, subscriptions) - response = simple.format_podcast_list(subscriptions, 'opml', request.user.username) - response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid + response = simple.format_podcast_list(subscriptions, "opml", request.user.username) + response["Content-Disposition"] = "attachment; filename=%s.opml" % device.uid return response @device_decorator @login_required -@allowed_methods(['POST']) +@allowed_methods(["POST"]) @transaction.atomic def delete(request, device): """ Mars a client as deleted, but does not permanently delete it """ @@ -246,14 +246,14 @@ def delete(request, device): device.deleted = True device.save() - return HttpResponseRedirect(reverse('devices')) + return HttpResponseRedirect(reverse("devices")) @login_required @device_decorator def delete_permanently(request, device): device.delete() - return HttpResponseRedirect(reverse('devices')) + return HttpResponseRedirect(reverse("devices")) @device_decorator @@ -269,17 +269,17 @@ def undelete(request, device): device.deleted = False device.save() - return HttpResponseRedirect(reverse('device', args=[device.uid])) + return HttpResponseRedirect(reverse("device", args=[device.uid])) @device_decorator @login_required -@allowed_methods(['POST']) +@allowed_methods(["POST"]) def sync(request, device): form = SyncForm(request.POST) if not form.is_valid(): - return HttpResponseBadRequest('invalid') + return HttpResponseBadRequest("invalid") try: target_uid = form.get_target() @@ -291,7 +291,7 @@ def sync(request, device): sync_user.delay(request.user.pk) - return HttpResponseRedirect(reverse('device', args=[device.uid])) + return HttpResponseRedirect(reverse("device", args=[device.uid])) @device_decorator @@ -299,18 +299,18 @@ def sync(request, device): def resync(request, device): """ Manually triggers a re-sync of a client """ sync_user.delay(request.user.pk) - messages.success(request, _('Your subscription will be updated in a moment.')) - return HttpResponseRedirect(reverse('device', args=[device.uid])) + messages.success(request, _("Your subscription will be updated in a moment.")) + return HttpResponseRedirect(reverse("device", args=[device.uid])) @device_decorator @login_required -@allowed_methods(['GET']) +@allowed_methods(["GET"]) def unsync(request, device): try: device.stop_sync() except ValueError as e: - messages.error(request, 'Could not unsync the device: {err}'.format(err=str(e))) + messages.error(request, "Could not unsync the device: {err}".format(err=str(e))) - return HttpResponseRedirect(reverse('device', args=[device.uid])) + return HttpResponseRedirect(reverse("device", args=[device.uid])) diff --git a/mygpo/users/views/registration.py b/mygpo/users/views/registration.py index 69d106c23..e1e86d4f7 100644 --- a/mygpo/users/views/registration.py +++ b/mygpo/users/views/registration.py @@ -19,7 +19,7 @@ from mygpo.users.models import UserProxy -USERNAME_MAXLEN = get_user_model()._meta.get_field('username').max_length +USERNAME_MAXLEN = get_user_model()._meta.get_field("username").max_length class DuplicateUsername(ValidationError): @@ -27,7 +27,7 @@ class DuplicateUsername(ValidationError): def __init__(self, username): self.username = username - super().__init__('The username {0} is already in use.'.format(username)) + super().__init__("The username {0} is already in use.".format(username)) class DuplicateEmail(ValidationError): @@ -35,15 +35,15 @@ class DuplicateEmail(ValidationError): def __init__(self, email): self.email = email - super().__init__('The email address {0} is already in use.'.format(email)) + super().__init__("The email address {0} is already in use.".format(email)) class UsernameValidator(RegexValidator): """ Validates that a username uses only allowed characters """ - regex = r'^\w[\w.+-]*$' - message = 'Invalid Username' - code = 'invalid-username' + regex = r"^\w[\w.+-]*$" + message = "Invalid Username" + code = "invalid-username" flags = re.ASCII @@ -59,19 +59,19 @@ class RegistrationForm(forms.Form): def clean(self): cleaned_data = super(RegistrationForm, self).clean() - password1 = cleaned_data.get('password1') - password2 = cleaned_data.get('password2') + password1 = cleaned_data.get("password1") + password2 = cleaned_data.get("password2") if not password1 or password1 != password2: - raise forms.ValidationError('Passwords do not match') + raise forms.ValidationError("Passwords do not match") class RegistrationView(FormView): """ View to register a new user """ - template_name = 'registration/registration_form.html' + template_name = "registration/registration_form.html" form_class = RegistrationForm - success_url = reverse_lazy('registration-complete') + success_url = reverse_lazy("registration-complete") def form_valid(self, form): """ called whene the form was POSTed and its contents were valid """ @@ -80,12 +80,12 @@ def form_valid(self, form): user = self.create_user(form) except ValidationError as e: - messages.error(self.request, '; '.join(e.messages)) - return HttpResponseRedirect(reverse('register')) + messages.error(self.request, "; ".join(e.messages)) + return HttpResponseRedirect(reverse("register")) except IntegrityError: - messages.error(self.request, _('Username or email address already in use')) - return HttpResponseRedirect(reverse('register')) + messages.error(self.request, _("Username or email address already in use")) + return HttpResponseRedirect(reverse("register")) send_activation_email(user, self.request) return super(RegistrationView, self).form_valid(form) @@ -94,15 +94,15 @@ def form_valid(self, form): def create_user(self, form): User = get_user_model() user = User() - username = form.cleaned_data['username'] + username = form.cleaned_data["username"] self._check_username(username) user.username = username - email_addr = form.cleaned_data['email'] + email_addr = form.cleaned_data["email"] user.email = email_addr - user.set_password(form.cleaned_data['password1']) + user.set_password(form.cleaned_data["password1"]) user.is_active = False user.full_clean() @@ -110,7 +110,7 @@ def create_user(self, form): user.save() except IntegrityError as e: - if 'django_auth_unique_email' in str(e): + if "django_auth_unique_email" in str(e): # this was not caught by the form validation, but now validates # the DB's unique constraint raise DuplicateEmail(email_addr) from e @@ -123,12 +123,12 @@ def create_user(self, form): return user def _check_username(self, username): - """ Check if the username is already in use + """Check if the username is already in use Until there is a case-insensitive constraint on usernames, it is necessary to check for existing usernames manually. This is not a perfect solution, but the chance that two people sign up with the same - username at the same time is low enough. """ + username at the same time is low enough.""" UserModel = get_user_model() users = UserModel.objects.filter(username__iexact=username) if users.exists(): @@ -138,7 +138,7 @@ def _check_username(self, username): class ActivationView(TemplateView): """ Activates an already registered user """ - template_name = 'registration/activation_failed.html' + template_name = "registration/activation_failed.html" def get(self, request, activation_key): User = get_user_model() @@ -150,15 +150,15 @@ def get(self, request, activation_key): except UserProxy.DoesNotExist: messages.error( request, - _('The activation link is either not ' 'valid or has already expired.'), + _("The activation link is either not " "valid or has already expired."), ) return super(ActivationView, self).get(request, activation_key) user.activate() messages.success( - request, _('Your user has been activated. ' 'You can log in now.') + request, _("Your user has been activated. " "You can log in now.") ) - return HttpResponseRedirect(reverse('login')) + return HttpResponseRedirect(reverse("login")) class ResendActivationForm(forms.Form): @@ -169,38 +169,38 @@ class ResendActivationForm(forms.Form): def clean(self): cleaned_data = super(ResendActivationForm, self).clean() - username = cleaned_data.get('username') - email = cleaned_data.get('email') + username = cleaned_data.get("username") + email = cleaned_data.get("email") if not username and not email: raise forms.ValidationError( - _('Either username or email address ' 'are required.') + _("Either username or email address " "are required.") ) class ResendActivationView(FormView): """ View to resend the activation email """ - template_name = 'registration/resend_activation.html' + template_name = "registration/resend_activation.html" form_class = ResendActivationForm - success_url = reverse_lazy('resent-activation') + success_url = reverse_lazy("resent-activation") def form_valid(self, form): """ called whene the form was POSTed and its contents were valid """ try: user = UserProxy.objects.all().by_username_or_email( - form.cleaned_data['username'], form.cleaned_data['email'] + form.cleaned_data["username"], form.cleaned_data["email"] ) except UserProxy.DoesNotExist: - messages.error(self.request, _('User does not exist.')) - return HttpResponseRedirect(reverse('resend-activation')) + messages.error(self.request, _("User does not exist.")) + return HttpResponseRedirect(reverse("resend-activation")) if user.profile.activation_key is None: messages.success( self.request, - _('Your account already has been ' 'activated. Go ahead and log in.'), + _("Your account already has been " "activated. Go ahead and log in."), ) send_activation_email(user, self.request) @@ -208,18 +208,18 @@ def form_valid(self, form): class ResentActivationView(TemplateView): - template_name = 'registration/resent_activation.html' + template_name = "registration/resent_activation.html" def send_activation_email(user, request): """ Sends the activation email for the given user """ - subj = render_to_string('registration/activation_email_subject.txt') + subj = render_to_string("registration/activation_email_subject.txt") # remove trailing newline added by render_to_string subj = subj.strip() msg = render_to_string( - 'registration/activation_email.txt', - {'site': RequestSite(request), 'activation_key': user.profile.activation_key}, + "registration/activation_email.txt", + {"site": RequestSite(request), "activation_key": user.profile.activation_key}, ) user.email_user(subj, msg) diff --git a/mygpo/users/views/settings.py b/mygpo/users/views/settings.py index 4301ee3e5..cc3d0f18c 100644 --- a/mygpo/users/views/settings.py +++ b/mygpo/users/views/settings.py @@ -25,36 +25,36 @@ @login_required @vary_on_cookie @cache_control(private=True) -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) def account(request): - if request.method == 'GET': + if request.method == "GET": site = RequestSite(request) - userpage_token = request.user.profile.get_token('userpage_token') + userpage_token = request.user.profile.get_token("userpage_token") profile_form = ProfileForm( { - 'twitter': request.user.profile.twitter, - 'about': request.user.profile.about, + "twitter": request.user.profile.twitter, + "about": request.user.profile.about, } ) form = UserAccountForm( { - 'email': request.user.email, - 'public': request.user.profile.settings.get_wksetting(PUBLIC_SUB_USER), + "email": request.user.email, + "public": request.user.profile.settings.get_wksetting(PUBLIC_SUB_USER), } ) return render( request, - 'account.html', + "account.html", { - 'site': site, - 'form': form, - 'profile_form': profile_form, - 'userpage_token': userpage_token, + "site": site, + "form": form, + "profile_form": profile_form, + "userpage_token": userpage_token, }, ) @@ -64,17 +64,17 @@ def account(request): if not form.is_valid(): raise ValueError( _( - 'Oops! Something went wrong. Please double-check the data you entered.' + "Oops! Something went wrong. Please double-check the data you entered." ) ) - if form.cleaned_data['password_current']: - if not request.user.check_password(form.cleaned_data['password_current']): - raise ValueError('Current password is incorrect') + if form.cleaned_data["password_current"]: + if not request.user.check_password(form.cleaned_data["password_current"]): + raise ValueError("Current password is incorrect") - request.user.set_password(form.cleaned_data['password1']) + request.user.set_password(form.cleaned_data["password1"]) - request.user.email = form.cleaned_data['email'] + request.user.email = form.cleaned_data["email"] try: request.user.save() @@ -82,12 +82,12 @@ def account(request): # TODO: which exception? messages.error(request, str(ex)) - messages.success(request, 'Account updated') + messages.success(request, "Account updated") except (ValueError, ValidationError) as e: messages.error(request, str(e)) - return render(request, 'account.html', {'form': form}) + return render(request, "account.html", {"form": form}) class ProfileView(View): @@ -101,17 +101,17 @@ def post(self, request): if not form.is_valid(): raise ValueError( _( - 'Oops! Something went wrong. Please double-check the data you entered.' + "Oops! Something went wrong. Please double-check the data you entered." ) ) - request.user.twitter = normalize_twitter(form.cleaned_data['twitter']) - request.user.about = strip_tags(form.cleaned_data['about']) + request.user.twitter = normalize_twitter(form.cleaned_data["twitter"]) + request.user.about = strip_tags(form.cleaned_data["about"]) request.user.save() - messages.success(request, _('Data updated')) + messages.success(request, _("Data updated")) - return HttpResponseRedirect(reverse('account') + '#profile') + return HttpResponseRedirect(reverse("account") + "#profile") class AccountRemoveGoogle(View): @@ -121,23 +121,23 @@ class AccountRemoveGoogle(View): def post(self, request): request.user.google_email = None request.user.save() - messages.success(request, _('Your account has been disconnected')) - return HttpResponseRedirect(reverse('account')) + messages.success(request, _("Your account has been disconnected")) + return HttpResponseRedirect(reverse("account")) @login_required @never_cache -@allowed_methods(['GET', 'POST']) +@allowed_methods(["GET", "POST"]) def delete_account(request): - if request.method == 'GET': - return render(request, 'delete_account.html') + if request.method == "GET": + return render(request, "delete_account.html") user = request.user user.is_active = False user.save() logout(request) - return render(request, 'deleted_account.html') + return render(request, "deleted_account.html") class DefaultPrivacySettings(View): @@ -150,7 +150,7 @@ def post(self, request): settings = request.user.profile.settings settings.set_setting(PUBLIC_SUB_USER.name, self.public) settings.save() - return HttpResponseRedirect(reverse('privacy')) + return HttpResponseRedirect(reverse("privacy")) class PodcastPrivacySettings(View): @@ -170,7 +170,7 @@ def post(self, request, podcast_id): settings.set_wksetting(PUBLIC_SUB_PODCAST, self.public) settings.save() - return HttpResponseRedirect(reverse('privacy')) + return HttpResponseRedirect(reverse("privacy")) @login_required @@ -179,7 +179,7 @@ def privacy(request): site = RequestSite(request) user = request.user - podcasts = Podcast.objects.filter(subscription__user=user).distinct('pk') + podcasts = Podcast.objects.filter(subscription__user=user).distinct("pk") private = UserSettings.objects.get_private_podcasts(user) subscriptions = [] @@ -189,13 +189,13 @@ def privacy(request): return render( request, - 'privacy.html', + "privacy.html", { - 'private_subscriptions': not request.user.profile.settings.get_wksetting( + "private_subscriptions": not request.user.profile.settings.get_wksetting( PUBLIC_SUB_USER ), - 'subscriptions': subscriptions, - 'domain': site.domain, + "subscriptions": subscriptions, + "domain": site.domain, }, ) @@ -208,14 +208,14 @@ def share(request): user = request.user - if 'public_subscriptions' in request.GET: - user.profile.subscriptions_token = '' + if "public_subscriptions" in request.GET: + user.profile.subscriptions_token = "" user.profile.save() - elif 'private_subscriptions' in request.GET: - user.profile.create_new_token('subscriptions_token') + elif "private_subscriptions" in request.GET: + user.profile.create_new_token("subscriptions_token") user.profile.save() - token = user.profile.get_token('subscriptions_token') + token = user.profile.get_token("subscriptions_token") - return render(request, 'share.html', {'site': site, 'token': token}) + return render(request, "share.html", {"site": site, "token": token}) diff --git a/mygpo/users/views/user.py b/mygpo/users/views/user.py index 2752161be..18ba15c6a 100644 --- a/mygpo/users/views/user.py +++ b/mygpo/users/views/user.py @@ -52,8 +52,8 @@ def get(self, request): return render( request, - 'login.html', - {'url': RequestSite(request), 'next': request.GET.get('next', '')}, + "login.html", + {"url": RequestSite(request), "next": request.GET.get("next", "")}, ) @method_decorator(never_cache) @@ -61,28 +61,28 @@ def post(self, request): """ Carries out the login, redirects to get if it fails """ # redirect target on successful login - next_page = request.POST.get('next', '') + next_page = request.POST.get("next", "") # redirect target on failed login - login_page = '{page}?next={next_page}'.format( - page=reverse('login'), next_page=next_page + login_page = "{page}?next={next_page}".format( + page=reverse("login"), next_page=next_page ) - username = request.POST.get('user', None) + username = request.POST.get("user", None) if not username: - messages.error(request, _('Username missing')) + messages.error(request, _("Username missing")) return HttpResponseRedirect(login_page) - password = request.POST.get('pwd', None) + password = request.POST.get("pwd", None) if not password: - messages.error(request, _('Password missing')) + messages.error(request, _("Password missing")) return HttpResponseRedirect(login_page) # find the user from the configured login systems, and verify pwd user = authenticate(username=username, password=password) if not user: - messages.error(request, _('Wrong username or password.')) + messages.error(request, _("Wrong username or password.")) return HttpResponseRedirect(login_page) if not user.is_active: @@ -90,8 +90,8 @@ def post(self, request): messages.error( request, _( - 'Please activate your account first. ' - 'We have just re-sent your activation email' + "Please activate your account first. " + "We have just re-sent your activation email" ), ) return HttpResponseRedirect(login_page) @@ -117,44 +117,44 @@ def post(self, request): @never_cache def restore_password(request): - if request.method == 'GET': + if request.method == "GET": form = RestorePasswordForm() - return render(request, 'restore_password.html', {'form': form}) + return render(request, "restore_password.html", {"form": form}) form = RestorePasswordForm(request.POST) if not form.is_valid(): - return HttpResponseRedirect('/login/') + return HttpResponseRedirect("/login/") try: user = UserProxy.objects.all().by_username_or_email( - form.cleaned_data['username'], form.cleaned_data['email'] + form.cleaned_data["username"], form.cleaned_data["email"] ) except UserProxy.DoesNotExist: - messages.error(request, _('User does not exist.')) - return render(request, 'password_reset_failed.html') + messages.error(request, _("User does not exist.")) + return render(request, "password_reset_failed.html") if not user.is_active: send_activation_email(user, request) messages.error( request, _( - 'Please activate your account first. ' - 'We have just re-sent your activation email' + "Please activate your account first. " + "We have just re-sent your activation email" ), ) - return HttpResponseRedirect(reverse('login')) + return HttpResponseRedirect(reverse("login")) site = RequestSite(request) pwd = random_token(length=16) user.set_password(pwd) user.save() - subject = render_to_string('reset-pwd-subj.txt', {'site': site}).strip() + subject = render_to_string("reset-pwd-subj.txt", {"site": site}).strip() message = render_to_string( - 'reset-pwd-msg.txt', {'username': user.username, 'site': site, 'password': pwd} + "reset-pwd-msg.txt", {"username": user.username, "site": site, "password": pwd} ) user.email_user(subject, message) - return render(request, 'password_reset.html') + return render(request, "password_reset.html") class GoogleLogin(View): @@ -171,23 +171,23 @@ class GoogleLoginCallback(TemplateView): def get(self, request): - if request.GET.get('error'): - messages.error(request, _('Login failed.')) - return HttpResponseRedirect(reverse('login')) + if request.GET.get("error"): + messages.error(request, _("Login failed.")) + return HttpResponseRedirect(reverse("login")) - code = request.GET.get('code') + code = request.GET.get("code") if not code: - messages.error(request, _('Login failed.')) - return HttpResponseRedirect(reverse('login')) + messages.error(request, _("Login failed.")) + return HttpResponseRedirect(reverse("login")) flow = get_google_oauth_flow(request) try: credentials = flow.step2_exchange(code) except FlowExchangeError: - messages.error(request, _('Login with Google is currently not possible.')) - logger.exception('Login with Google failed') - return HttpResponseRedirect(reverse('login')) + messages.error(request, _("Login with Google is currently not possible.")) + logger.exception("Login with Google failed") + return HttpResponseRedirect(reverse("login")) email = self._get_email(credentials.token_response) @@ -198,8 +198,8 @@ def get(self, request): messages.success( request, _( - 'Your account has been connected with ' - '{google}. Open Settings to change this.'.format(google=email) + "Your account has been connected with " + "{google}. Open Settings to change this.".format(google=email) ), ) return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT) @@ -214,25 +214,25 @@ def get(self, request): messages.error( request, _( - 'No account connected with your Google ' - 'account %s. Please log in to connect.' % email + "No account connected with your Google " + "account %s. Please log in to connect." % email ), ) return HttpResponseRedirect( - '{login}?next={connect}'.format( - login=reverse('login'), connect=reverse('login-google') + "{login}?next={connect}".format( + login=reverse("login"), connect=reverse("login-google") ) ) # Log in user # TODO: this should probably be replaced with a call to authenticate() # http://stackoverflow.com/questions/6034763/django-attributeerror-user-object-has-no-attribute-backend-but-it-does - user.backend = 'django.contrib.auth.backends.ModelBackend' + user.backend = "django.contrib.auth.backends.ModelBackend" login(request, user) return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT) def _get_email(self, response): - USERINFO_URL = 'https://www.googleapis.com/userinfo/email?alt=json' - headers = {'Authorization': 'Bearer ' + response['access_token']} + USERINFO_URL = "https://www.googleapis.com/userinfo/email?alt=json" + headers = {"Authorization": "Bearer " + response["access_token"]} resp = requests.get(USERINFO_URL, headers=headers).json() - return resp['data']['email'] + return resp["data"]["email"] diff --git a/mygpo/usersettings/admin.py b/mygpo/usersettings/admin.py index c23dae046..d655deb07 100644 --- a/mygpo/usersettings/admin.py +++ b/mygpo/usersettings/admin.py @@ -7,11 +7,11 @@ class PodcastConfigAdmin(admin.ModelAdmin): # configuration for the list view - list_display = ('user', 'content_object') + list_display = ("user", "content_object") # fetch the related objects for the fields in list_display - list_select_related = ('user', 'content_object') + list_select_related = ("user", "content_object") - raw_id_fields = ('user',) + raw_id_fields = ("user",) show_full_result_count = False diff --git a/mygpo/usersettings/converters.py b/mygpo/usersettings/converters.py index a979d5d28..5cc628d00 100644 --- a/mygpo/usersettings/converters.py +++ b/mygpo/usersettings/converters.py @@ -1,5 +1,5 @@ class ScopeConverter: - regex = 'account|device|podcast|episode' + regex = "account|device|podcast|episode" def to_python(self, value): return value diff --git a/mygpo/usersettings/migrations/0001_initial.py b/mygpo/usersettings/migrations/0001_initial.py index daba9eaaa..e4dbdbbe3 100644 --- a/mygpo/usersettings/migrations/0001_initial.py +++ b/mygpo/usersettings/migrations/0001_initial.py @@ -9,35 +9,35 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0001_initial'), + ("contenttypes", "0001_initial"), ] operations = [ migrations.CreateModel( - name='UserSettings', + name="UserSettings", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('settings', models.TextField(default='{}')), - ('object_id', models.UUIDField(max_length=32, null=True, blank=True)), + ("settings", models.TextField(default="{}")), + ("object_id", models.UUIDField(max_length=32, null=True, blank=True)), ( - 'content_type', + "content_type", models.ForeignKey( blank=True, - to='contenttypes.ContentType', + to="contenttypes.ContentType", null=True, on_delete=models.PROTECT, ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -47,8 +47,8 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='usersettings', - unique_together=set([('user', 'content_type', 'object_id')]), + name="usersettings", + unique_together=set([("user", "content_type", "object_id")]), ), # PostgreSQL does not consider null values for unique constraints; # UserSettings for Users have no content_object; the following ensures @@ -56,12 +56,12 @@ class Migration(migrations.Migration): migrations.RunSQL( [ ( - 'CREATE UNIQUE INDEX usersettings_unique_null ' - 'ON usersettings_usersettings (user_id) ' - 'WHERE content_type_id IS NULL;', + "CREATE UNIQUE INDEX usersettings_unique_null " + "ON usersettings_usersettings (user_id) " + "WHERE content_type_id IS NULL;", None, ) ], - [('DROP INDEX IF EXISTS usersettings_unique_null;', None)], + [("DROP INDEX IF EXISTS usersettings_unique_null;", None)], ), ] diff --git a/mygpo/usersettings/migrations/0002_move_existing.py b/mygpo/usersettings/migrations/0002_move_existing.py index ac6189db0..d61231987 100644 --- a/mygpo/usersettings/migrations/0002_move_existing.py +++ b/mygpo/usersettings/migrations/0002_move_existing.py @@ -7,7 +7,7 @@ def move_podcastsettings(apps, schema_editor): PodcastConfig = apps.get_model("subscriptions", "PodcastConfig") UserSettings = apps.get_model("usersettings", "UserSettings") - ContentType = apps.get_model('contenttypes', 'ContentType') + ContentType = apps.get_model("contenttypes", "ContentType") for cfg in PodcastConfig.objects.all(): if not json.loads(cfg.settings): @@ -17,9 +17,9 @@ def move_podcastsettings(apps, schema_editor): user=cfg.user, # we can't get the contenttype from cfg.podcast as it would be a # different model - content_type=ContentType.objects.get(app_label='podcasts', model='podcast'), + content_type=ContentType.objects.get(app_label="podcasts", model="podcast"), object_id=cfg.podcast.pk, - defaults={'settings': cfg.settings}, + defaults={"settings": cfg.settings}, ) @@ -36,16 +36,16 @@ def move_usersettings(apps, schema_editor): user=profile.user, content_type=None, object_id=None, - defaults={'settings': profile.settings}, + defaults={"settings": profile.settings}, ) class Migration(migrations.Migration): dependencies = [ - ('usersettings', '0001_initial'), - ('subscriptions', '0002_unique_constraint'), - ('users', '0011_syncgroup_blank'), + ("usersettings", "0001_initial"), + ("subscriptions", "0002_unique_constraint"), + ("users", "0011_syncgroup_blank"), ] operations = [ diff --git a/mygpo/usersettings/migrations/0003_meta_verbose_name.py b/mygpo/usersettings/migrations/0003_meta_verbose_name.py index 91a61110b..5cfcaef2b 100644 --- a/mygpo/usersettings/migrations/0003_meta_verbose_name.py +++ b/mygpo/usersettings/migrations/0003_meta_verbose_name.py @@ -6,14 +6,14 @@ class Migration(migrations.Migration): - dependencies = [('usersettings', '0002_move_existing')] + dependencies = [("usersettings", "0002_move_existing")] operations = [ migrations.AlterModelOptions( - name='usersettings', + name="usersettings", options={ - 'verbose_name': 'User Settings', - 'verbose_name_plural': 'User Settings', + "verbose_name": "User Settings", + "verbose_name_plural": "User Settings", }, ) ] diff --git a/mygpo/usersettings/migrations/0004_django_uuidfield.py b/mygpo/usersettings/migrations/0004_django_uuidfield.py index 721cc8be8..75d875877 100644 --- a/mygpo/usersettings/migrations/0004_django_uuidfield.py +++ b/mygpo/usersettings/migrations/0004_django_uuidfield.py @@ -4,12 +4,12 @@ class Migration(migrations.Migration): - dependencies = [('usersettings', '0003_meta_verbose_name')] + dependencies = [("usersettings", "0003_meta_verbose_name")] operations = [ migrations.AlterField( - model_name='usersettings', - name='object_id', + model_name="usersettings", + name="object_id", field=models.UUIDField(null=True, blank=True), ) ] diff --git a/mygpo/usersettings/models.py b/mygpo/usersettings/models.py index 3177c3c62..21111fff3 100644 --- a/mygpo/usersettings/models.py +++ b/mygpo/usersettings/models.py @@ -30,9 +30,9 @@ def get_private_podcasts(self, user): return private def get_for_scope(self, user, scope): - """ Returns the settings object for the given user and scope obj + """Returns the settings object for the given user and scope obj - If scope is None, the settings for the user are returned """ + If scope is None, the settings for the user are returned""" if scope is None: content_type = None object_id = None @@ -64,15 +64,15 @@ class UserSettings(models.Model): ContentType, null=True, blank=True, on_delete=models.PROTECT ) object_id = models.UUIDField(null=True, blank=True) - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") - settings = models.TextField(null=False, default='{}') + settings = models.TextField(null=False, default="{}") class Meta: - unique_together = [['user', 'content_type', 'object_id']] + unique_together = [["user", "content_type", "object_id"]] - verbose_name_plural = 'User Settings' - verbose_name = 'User Settings' + verbose_name_plural = "User Settings" + verbose_name = "User Settings" objects = UserSettingsManager() @@ -81,7 +81,7 @@ def get_wksetting(self, setting): try: settings = json.loads(self.settings) except ValueError as ex: - logger.warning('Decoding settings failed: {msg}'.format(msg=str(ex))) + logger.warning("Decoding settings failed: {msg}".format(msg=str(ex))) return None return settings.get(setting.name, setting.default) @@ -90,7 +90,7 @@ def set_wksetting(self, setting, value): try: settings = json.loads(self.settings) except ValueError as ex: - logger.warning('Decoding settings failed: {msg}'.format(msg=str(ex))) + logger.warning("Decoding settings failed: {msg}".format(msg=str(ex))) settings = {} settings[setting.name] = value self.settings = json.dumps(settings) diff --git a/mygpo/usersettings/tests.py b/mygpo/usersettings/tests.py index d4ffbdbd5..add880cf9 100644 --- a/mygpo/usersettings/tests.py +++ b/mygpo/usersettings/tests.py @@ -16,9 +16,9 @@ class TestAPI(TestCase): def setUp(self): self.user, pwd = create_user() - self.podcast_url = 'http://example.com/podcast.rss' - self.episode_url = 'http://example.com/podcast/episode-1.mp3' - self.uid = 'client-uid' + self.podcast_url = "http://example.com/podcast.rss" + self.episode_url = "http://example.com/podcast/episode-1.mp3" + self.uid = "client-uid" self.podcast = Podcast.objects.get_or_create_for_url(self.podcast_url).object self.episode = Episode.objects.get_or_create_for_url( @@ -29,7 +29,7 @@ def setUp(self): id=uuid.uuid1(), user=self.user, uid=self.uid ) self.client = TClient() - self.extra = {'HTTP_AUTHORIZATION': create_auth_string(self.user.username, pwd)} + self.extra = {"HTTP_AUTHORIZATION": create_auth_string(self.user.username, pwd)} def tearDown(self): self.user.delete() @@ -38,42 +38,42 @@ def tearDown(self): def test_user_settings(self): """ Create, update and verify settings for the user """ - url = self.get_url(self.user.username, 'account') + url = self.get_url(self.user.username, "account") self._do_test_url(url) def test_podcast_settings(self): - url = self.get_url(self.user.username, 'podcast', {'podcast': self.podcast_url}) + url = self.get_url(self.user.username, "podcast", {"podcast": self.podcast_url}) self._do_test_url(url) def test_episode_settings(self): url = self.get_url( self.user.username, - 'episode', - {'podcast': self.podcast_url, 'episode': self.episode_url}, + "episode", + {"podcast": self.podcast_url, "episode": self.episode_url}, ) self._do_test_url(url) def test_client_settings(self): - url = self.get_url(self.user.username, 'device', {'device': self.uid}) + url = self.get_url(self.user.username, "device", {"device": self.uid}) self._do_test_url(url) def _do_test_url(self, url): # set settings - settings = {'set': {'a': 'b', 'c': 'd'}} + settings = {"set": {"a": "b", "c": "d"}} resp = self.client.post( url, json.dumps(settings), - content_type='application/octet-stream', + content_type="application/octet-stream", **self.extra, ) self.assertEqual(resp.status_code, 200, resp.content) # update settings - settings = {'set': {'a': 'x'}, 'remove': ['c']} + settings = {"set": {"a": "x"}, "remove": ["c"]} resp = self.client.post( url, json.dumps(settings), - content_type='application/octet-stream', + content_type="application/octet-stream", **self.extra, ) self.assertEqual(resp.status_code, 200, resp.content) @@ -81,8 +81,8 @@ def _do_test_url(self, url): # get settings resp = self.client.get(url, **self.extra) self.assertEqual(resp.status_code, 200, resp.content) - self.assertEqual(json.loads(resp.content.decode('utf-8')), {'a': 'x'}) + self.assertEqual(json.loads(resp.content.decode("utf-8")), {"a": "x"}) def get_url(self, username, scope, params={}): - url = reverse('settings-api', kwargs={'username': username, 'scope': scope}) - return '{url}?{params}'.format(url=url, params=urllib.parse.urlencode(params)) + url = reverse("settings-api", kwargs={"username": username, "scope": scope}) + return "{url}?{params}".format(url=url, params=urllib.parse.urlencode(params)) diff --git a/mygpo/utils.py b/mygpo/utils.py index 16a8dc69e..0d5e4f0ce 100644 --- a/mygpo/utils.py +++ b/mygpo/utils.py @@ -65,12 +65,12 @@ def format_time(value): try: dt = datetime.utcfromtimestamp(value) except (ValueError, TypeError): - return '' + return "" if dt.hour == 0: - return dt.strftime('%M:%S') + return dt.strftime("%M:%S") else: - return dt.strftime('%H:%M:%S') + return dt.strftime("%H:%M:%S") def parse_time(value): @@ -85,16 +85,16 @@ def parse_time(value): 3910 """ if value is None: - raise ValueError('None value in parse_time') + raise ValueError("None value in parse_time") if isinstance(value, int): # Don't need to parse already-converted time value return value - if value == '': - raise ValueError('Empty valueing in parse_time') + if value == "": + raise ValueError("Empty valueing in parse_time") - for format in ('%H:%M:%S', '%M:%S'): + for format in ("%H:%M:%S", "%M:%S"): try: t = time.strptime(value, format) return t.tm_hour * 60 * 60 + t.tm_min * 60 + t.tm_sec @@ -117,21 +117,21 @@ def parse_bool(val): """ if isinstance(val, bool): return val - if val.lower() == 'true': + if val.lower() == "true": return True return False -def progress(val, max_val, status_str='', max_width=50, stream=sys.stdout): +def progress(val, max_val, status_str="", max_width=50, stream=sys.stdout): factor = float(val) / max_val if max_val > 0 else 0 # progress as percentage - percentage_str = '{val:.2%}'.format(val=factor) + percentage_str = "{val:.2%}".format(val=factor) # progress bar filled with #s factor = min(int(factor * max_width), max_width) - progress_str = '#' * factor + ' ' * (max_width - factor) + progress_str = "#" * factor + " " * (max_width - factor) # insert percentage into bar percentage_start = int((max_width - len(percentage_str)) / 2) @@ -141,10 +141,10 @@ def progress(val, max_val, status_str='', max_width=50, stream=sys.stdout): + progress_str[percentage_start + len(percentage_str) :] ) - print('\r', end=' ', file=stream) + print("\r", end=" ", file=stream) print( - '[ %s ] %s / %s | %s' % (progress_str, val, max_val, status_str), - end=' ', + "[ %s ] %s / %s | %s" % (progress_str, val, max_val, status_str), + end=" ", file=stream, ) stream.flush() @@ -189,7 +189,7 @@ def parse_range(s, min, max, default=None): def get_timestamp(datetime_obj): - """ Returns the timestamp as an int for the given datetime object + """Returns the timestamp as an int for the given datetime object >>> get_timestamp(datetime(2011, 4, 7, 9, 30, 6)) 1302168606 @@ -200,11 +200,11 @@ def get_timestamp(datetime_obj): return int(time.mktime(datetime_obj.timetuple())) -re_url = re.compile('^https?://') +re_url = re.compile("^https?://") def is_url(string): - """ Returns true if a string looks like an URL + """Returns true if a string looks like an URL >>> is_url('http://example.com/some-path/file.xml') True @@ -281,17 +281,17 @@ def url_add_authentication(url, username, password): >>> url_add_authentication('http://x.org/', 'a b', 'c d') 'http://a%20b:c%20d@x.org/' """ - if username is None or username == '': + if username is None or username == "": return url # Relaxations of the strict quoting rules (bug 1521): # 1. Accept '@' in username and password # 2. Acecpt ':' in password only - username = urllib.parse.quote(username, safe='@') + username = urllib.parse.quote(username, safe="@") if password is not None: - password = urllib.parse.quote(password, safe='@:') - auth_string = ':'.join((username, password)) + password = urllib.parse.quote(password, safe="@:") + auth_string = ":".join((username, password)) else: auth_string = username @@ -299,7 +299,7 @@ def url_add_authentication(url, username, password): url_parts = list(urllib.parse.urlsplit(url)) # url_parts[1] is the HOST part of the URL - url_parts[1] = '@'.join((auth_string, url_parts[1])) + url_parts[1] = "@".join((auth_string, url_parts[1])) return urllib.parse.urlunsplit(url_parts) @@ -323,7 +323,7 @@ def urlopen(url, headers=None, data=None): else: headers = dict(headers) - headers.update({'User-agent': settings.USER_AGENT}) + headers.update({"User-agent": settings.USER_AGENT}) request = urllib.request.Request(url, data=data, headers=headers) return opener.open(request) @@ -366,16 +366,16 @@ def username_password_from_url(url): (None, None) """ if type(url) not in (str, str): - raise ValueError('URL has to be a string or unicode object.') + raise ValueError("URL has to be a string or unicode object.") (username, password) = (None, None) (scheme, netloc, path, params, query, fragment) = urllib.parse.urlparse(url) - if '@' in netloc: - (authentication, netloc) = netloc.rsplit('@', 1) - if ':' in authentication: - (username, password) = authentication.split(':', 1) + if "@" in netloc: + (authentication, netloc) = netloc.rsplit("@", 1) + if ":" in authentication: + (username, password) = authentication.split(":", 1) # RFC1738 dictates that we should not allow ['/', '@', ':'] # characters in the username and password field (Section 3.1): @@ -426,8 +426,8 @@ def url_strip_authentication(url): # url_parts[1] is the HOST part of the URL # Remove existing authentication data - if '@' in url_parts[1]: - url_parts[1] = url_parts[1].rsplit('@', 1)[1] + if "@" in url_parts[1]: + url_parts[1] = url_parts[1].rsplit("@", 1)[1] return urllib.parse.urlunsplit(url_parts) @@ -441,7 +441,7 @@ def get_git_head(): try: pr = subprocess.Popen( - '/usr/bin/git log -n 1 --oneline'.split(), + "/usr/bin/git log -n 1 --oneline".split(), cwd=settings.BASE_DIR, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -454,9 +454,9 @@ def get_git_head(): if err: return None, None - outs = [o.decode('utf-8') for o in out.split()] + outs = [o.decode("utf-8") for o in out.split()] commit = outs[0] - msg = ' '.join(outs[1:]) + msg = " ".join(outs[1:]) return commit, msg @@ -464,12 +464,12 @@ def parse_request_body(request): """ returns the parsed request body, handles gzip encoding """ raw_body = request.body - content_enc = request.META.get('HTTP_CONTENT_ENCODING') + content_enc = request.META.get("HTTP_CONTENT_ENCODING") - if content_enc == 'gzip': + if content_enc == "gzip": raw_body = zlib.decompress(raw_body) - return json.loads(raw_body.decode('utf-8')) + return json.loads(raw_body.decode("utf-8")) def normalize_feed_url(url): @@ -539,13 +539,13 @@ def normalize_feed_url(url): # keystrokes that you have to use. # Feel free to suggest other useful prefixes, and I'll add them here. PREFIXES = { - 'fb:': 'http://feeds.feedburner.com/%s', - 'yt:': 'http://www.youtube.com/rss/user/%s/videos.rss', - 'sc:': 'http://soundcloud.com/%s', - 'fm4od:': 'http://onapp1.orf.at/webcam/fm4/fod/%s.xspf', + "fb:": "http://feeds.feedburner.com/%s", + "yt:": "http://www.youtube.com/rss/user/%s/videos.rss", + "sc:": "http://soundcloud.com/%s", + "fm4od:": "http://onapp1.orf.at/webcam/fm4/fod/%s.xspf", # YouTube playlists. To get a list of playlists per-user, use: # https://gdata.youtube.com/feeds/api/users//playlists - 'ytpl:': 'http://gdata.youtube.com/feeds/api/playlists/%s', + "ytpl:": "http://gdata.youtube.com/feeds/api/playlists/%s", } for prefix, expansion in PREFIXES.items(): @@ -554,8 +554,8 @@ def normalize_feed_url(url): break # Assume HTTP for URLs without scheme - if not '://' in url: - url = 'http://' + url + if not "://" in url: + url = "http://" + url scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url) @@ -563,21 +563,21 @@ def normalize_feed_url(url): scheme, netloc = scheme.lower(), netloc.lower() # encode non-encoded characters - path = urllib.parse.quote(path, '/%') - query = urllib.parse.quote_plus(query, ':&=') + path = urllib.parse.quote(path, "/%") + query = urllib.parse.quote_plus(query, ":&=") # Remove authentication to protect users' privacy - netloc = netloc.rsplit('@', 1)[-1] + netloc = netloc.rsplit("@", 1)[-1] # Normalize empty paths to "/" - if path == '': - path = '/' + if path == "": + path = "/" # feed://, itpc:// and itms:// are really http:// - if scheme in ('feed', 'itpc', 'itms'): - scheme = 'http' + if scheme in ("feed", "itpc", "itms"): + scheme = "http" - if scheme not in ('http', 'https', 'ftp', 'file'): + if scheme not in ("http", "https", "ftp", "file"): return None # urlunsplit might return "a slighty different, but equivalent URL" @@ -587,7 +587,7 @@ def normalize_feed_url(url): def edit_link(obj): """ Return the link to the Django Admin Edit page """ return reverse( - 'admin:%s_%s_change' % (obj._meta.app_label, obj._meta.model_name), + "admin:%s_%s_change" % (obj._meta.app_label, obj._meta.model_name), args=(obj.pk,), ) @@ -609,7 +609,7 @@ def to_maxlength(cls, field, val): if orig_length > max_length: val = val[:max_length] logger.warning( - '%s.%s length reduced from %d to %d', + "%s.%s length reduced from %d to %d", cls.__name__, field, orig_length, @@ -620,7 +620,7 @@ def to_maxlength(cls, field, val): def get_domain(url): - """ Returns the domain name of a URL + """Returns the domain name of a URL >>> get_domain('http://example.com') 'example.com' @@ -630,7 +630,7 @@ def get_domain(url): """ netloc = urllib.parse.urlparse(url).netloc try: - port_idx = netloc.index(':') + port_idx = netloc.index(":") return netloc[:port_idx] except ValueError: @@ -640,48 +640,48 @@ def get_domain(url): def set_ordered_entries( obj, new_entries, existing, EntryClass, value_name, parent_name ): - """ Update the object's entries to the given list + """Update the object's entries to the given list 'new_entries' should be a list of objects that are later wrapped in EntryClass instances. 'value_name' is the name of the EntryClass property that contains the values; 'parent_name' is the one that references obj. Entries that do not exist are created. Existing entries that are not in - 'new_entries' are deleted. """ + 'new_entries' are deleted.""" - logger.info('%d existing entries', len(existing)) + logger.info("%d existing entries", len(existing)) - logger.info('%d new entries', len(new_entries)) + logger.info("%d new entries", len(new_entries)) with transaction.atomic(): max_order = max([s.order for s in existing.values()] + [len(new_entries)]) - logger.info('Renumbering entries starting from %d', max_order + 1) + logger.info("Renumbering entries starting from %d", max_order + 1) for n, entry in enumerate(existing.values(), max_order + 1): entry.order = n entry.save() - logger.info('%d existing entries', len(existing)) + logger.info("%d existing entries", len(existing)) for n, entry in enumerate(new_entries): try: e = existing.pop(entry) - logger.info('Updating existing entry %d: %s', n, entry) + logger.info("Updating existing entry %d: %s", n, entry) e.order = n e.save() except KeyError: - logger.info('Creating new entry %d: %s', n, entry) + logger.info("Creating new entry %d: %s", n, entry) try: links = {value_name: entry, parent_name: obj} from mygpo.podcasts.models import ScopedModel if issubclass(EntryClass, ScopedModel): - links['scope'] = obj.scope + links["scope"] = obj.scope EntryClass.objects.create(order=n, **links) except IntegrityError as ie: - logger.warning('Could not create enry for %s: %s', obj, ie) + logger.warning("Could not create enry for %s: %s", obj, ie) with transaction.atomic(): delete = [s.pk for s in existing.values()] - logger.info('Deleting %d entries', len(delete)) + logger.info("Deleting %d entries", len(delete)) EntryClass.objects.filter(id__in=delete).delete() diff --git a/mygpo/votes/admin.py b/mygpo/votes/admin.py index 7d0232e8b..2d56c016f 100644 --- a/mygpo/votes/admin.py +++ b/mygpo/votes/admin.py @@ -7,4 +7,4 @@ class VoteInline(GenericTabularInline): """ Inline Admin model for votes """ model = models.Vote - raw_id_fields = ('user',) + raw_id_fields = ("user",) diff --git a/mygpo/votes/migrations/0001_initial.py b/mygpo/votes/migrations/0001_initial.py index dc77cb932..9020ba788 100644 --- a/mygpo/votes/migrations/0001_initial.py +++ b/mygpo/votes/migrations/0001_initial.py @@ -10,34 +10,34 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0001_initial'), + ("contenttypes", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Vote', + name="Vote", fields=[ ( - 'id', + "id", models.AutoField( - verbose_name='ID', + verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), - ('object_id', models.UUIDField(max_length=32)), - ('created', models.DateTimeField()), - ('modified', models.DateTimeField()), + ("object_id", models.UUIDField(max_length=32)), + ("created", models.DateTimeField()), + ("modified", models.DateTimeField()), ( - 'content_type', + "content_type", models.ForeignKey( - to='contenttypes.ContentType', + to="contenttypes.ContentType", on_delete=django.db.models.deletion.PROTECT, ), ), ( - 'user', + "user", models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE ), @@ -47,6 +47,6 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='vote', unique_together=set([('user', 'content_type', 'object_id')]) + name="vote", unique_together=set([("user", "content_type", "object_id")]) ), ] diff --git a/mygpo/votes/migrations/0002_updateinfomodel.py b/mygpo/votes/migrations/0002_updateinfomodel.py index 4a5a3e967..aefdb6848 100644 --- a/mygpo/votes/migrations/0002_updateinfomodel.py +++ b/mygpo/votes/migrations/0002_updateinfomodel.py @@ -6,17 +6,17 @@ class Migration(migrations.Migration): - dependencies = [('votes', '0001_initial')] + dependencies = [("votes", "0001_initial")] operations = [ migrations.AlterField( - model_name='vote', - name='created', + model_name="vote", + name="created", field=models.DateTimeField(auto_now_add=True), ), migrations.AlterField( - model_name='vote', - name='modified', + model_name="vote", + name="modified", field=models.DateTimeField(auto_now=True), ), ] diff --git a/mygpo/votes/migrations/0003_django_uuidfield.py b/mygpo/votes/migrations/0003_django_uuidfield.py index 3b6a4ad17..6d1d91bb9 100644 --- a/mygpo/votes/migrations/0003_django_uuidfield.py +++ b/mygpo/votes/migrations/0003_django_uuidfield.py @@ -4,10 +4,10 @@ class Migration(migrations.Migration): - dependencies = [('votes', '0002_updateinfomodel')] + dependencies = [("votes", "0002_updateinfomodel")] operations = [ migrations.AlterField( - model_name='vote', name='object_id', field=models.UUIDField() + model_name="vote", name="object_id", field=models.UUIDField() ) ] diff --git a/mygpo/votes/models.py b/mygpo/votes/models.py index 36474e90d..8555e7f32 100644 --- a/mygpo/votes/models.py +++ b/mygpo/votes/models.py @@ -16,18 +16,18 @@ class Vote(UpdateInfoModel): content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) # this should suit UUID and integer primary keys object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") class Meta: unique_together = [ # a user can only vote once per object - ('user', 'content_type', 'object_id') + ("user", "content_type", "object_id") ] class VoteMixin(models.Model): - votes = GenericRelation(Vote, related_query_name='votes') + votes = GenericRelation(Vote, related_query_name="votes") class Meta: abstract = True diff --git a/mygpo/web/auth.py b/mygpo/web/auth.py index 561328cca..0a6dec3c9 100644 --- a/mygpo/web/auth.py +++ b/mygpo/web/auth.py @@ -27,24 +27,24 @@ def authenticate(self, request, username=None, password=None): def get_google_oauth_flow(request): - """ Prepare an OAuth 2.0 flow + """Prepare an OAuth 2.0 flow - https://developers.google.com/api-client-library/python/guide/aaa_oauth """ + https://developers.google.com/api-client-library/python/guide/aaa_oauth""" from oauth2client.client import OAuth2WebServerFlow site = RequestSite(request) - callback = 'http{s}://{domain}{callback}'.format( - s='s' if request.is_secure() else '', + callback = "http{s}://{domain}{callback}".format( + s="s" if request.is_secure() else "", domain=site.domain, - callback=reverse('login-google-callback'), + callback=reverse("login-google-callback"), ) flow = OAuth2WebServerFlow( client_id=settings.GOOGLE_CLIENT_ID, client_secret=settings.GOOGLE_CLIENT_SECRET, - scope='https://www.googleapis.com/auth/userinfo.email', + scope="https://www.googleapis.com/auth/userinfo.email", redirect_uri=callback, ) diff --git a/mygpo/web/forms.py b/mygpo/web/forms.py index 30db8224e..f603ff646 100644 --- a/mygpo/web/forms.py +++ b/mygpo/web/forms.py @@ -19,31 +19,31 @@ class UserAccountForm(forms.Form): """ email = forms.EmailField( - label=_('E-Mail address'), - widget=forms.TextInput(attrs={'class': 'input input-sm form-control'}), + label=_("E-Mail address"), + widget=forms.TextInput(attrs={"class": "input input-sm form-control"}), required=True, ) password_current = forms.CharField( - label=_('Current password'), + label=_("Current password"), widget=forms.PasswordInput( - render_value=False, attrs={'class': 'input input-sm form-control'} + render_value=False, attrs={"class": "input input-sm form-control"} ), required=False, ) password1 = forms.CharField( - label=_('New password'), + label=_("New password"), widget=forms.PasswordInput( - render_value=False, attrs={'class': 'input input-sm form-control'} + render_value=False, attrs={"class": "input input-sm form-control"} ), required=False, ) password2 = forms.CharField( - label=_('Confirm password'), + label=_("Confirm password"), widget=forms.PasswordInput( - render_value=False, attrs={'class': 'input input-sm form-control'} + render_value=False, attrs={"class": "input input-sm form-control"} ), required=False, ) @@ -52,15 +52,15 @@ def is_valid(self): if not super(UserAccountForm, self).is_valid(): return False - pw1 = self.cleaned_data['password1'] - pw2 = self.cleaned_data['password2'] + pw1 = self.cleaned_data["password1"] + pw2 = self.cleaned_data["password2"] - if self.cleaned_data['password_current'] or pw1 or pw2: + if self.cleaned_data["password_current"] or pw1 or pw2: - if self.cleaned_data['password_current'] == '': + if self.cleaned_data["password_current"] == "": return False # must give current password - if pw1 == '': + if pw1 == "": return False # cant set empty password if pw1 != pw2: @@ -71,16 +71,16 @@ def is_valid(self): class ProfileForm(forms.Form): twitter = forms.CharField( - label=_('Twitter'), - widget=forms.TextInput(attrs={'class': 'input input-sm form-control'}), + label=_("Twitter"), + widget=forms.TextInput(attrs={"class": "input input-sm form-control"}), required=False, ) about = forms.CharField( - label=_('A few words about you'), + label=_("A few words about you"), required=False, - widget=forms.Textarea(attrs={'class': 'input input-sm form-control'}), - help_text='You can use Markdown', + widget=forms.Textarea(attrs={"class": "input input-sm form-control"}), + help_text="You can use Markdown", ) @@ -91,23 +91,23 @@ class DeviceForm(forms.Form): name = forms.CharField( max_length=100, - label=_('Name'), + label=_("Name"), widget=forms.TextInput( - attrs={'class': 'input input-sm form-control', 'placeholder': 'Device Name'} + attrs={"class": "input input-sm form-control", "placeholder": "Device Name"} ), ) type = forms.ChoiceField( choices=Client.TYPES, - label=_('Type'), - widget=forms.Select(attrs={'class': 'input input-sm form-control'}), + label=_("Type"), + widget=forms.Select(attrs={"class": "input input-sm form-control"}), ) uid = forms.CharField( max_length=50, - label=_('Device ID'), + label=_("Device ID"), widget=forms.TextInput( attrs={ - 'class': 'input input-sm form-control', - 'placeholder': _('ID on device'), + "class": "input input-sm form-control", + "placeholder": _("ID on device"), } ), ) @@ -120,7 +120,7 @@ class PrivacyForm(forms.Form): """ public = forms.BooleanField( - required=False, label=_('Share this subscription with other users (public)') + required=False, label=_("Share this subscription with other users (public)") ) @@ -130,12 +130,12 @@ class SyncForm(forms.Form): """ targets = forms.CharField( - widget=forms.Select(attrs={'class': 'input input-sm form-control'}) + widget=forms.Select(attrs={"class": "input input-sm form-control"}) ) - def set_targets(self, sync_targets, label=''): + def set_targets(self, sync_targets, label=""): targets = list(map(self.sync_target_choice, sync_targets)) - self.fields['targets'] = forms.ChoiceField(choices=targets, label=label) + self.fields["targets"] = forms.ChoiceField(choices=targets, label=label) def sync_target_choice(self, target): """ @@ -151,7 +151,7 @@ def sync_target_choice(self, target): return (target.uid, target.name) elif isinstance(target, list): - return (target[0].uid, ', '.join(d.name for d in target)) + return (target[0].uid, ", ".join(d.name for d in target)) def get_target(self): """ @@ -159,26 +159,26 @@ def get_target(self): in the form. """ if not self.is_valid(): - logger.warning('no target given in SyncForm') - raise ValueError(_('No device selected')) + logger.warning("no target given in SyncForm") + raise ValueError(_("No device selected")) - target = self.cleaned_data['targets'] + target = self.cleaned_data["targets"] return target class ResendActivationForm(forms.Form): username = forms.CharField( - max_length=100, label=_('Please enter your username'), required=False + max_length=100, label=_("Please enter your username"), required=False ) email = forms.CharField( max_length=100, - label=_('or the email address used while registering'), + label=_("or the email address used while registering"), required=False, ) class RestorePasswordForm(forms.Form): - username = forms.CharField(max_length=100, label=_('Username'), required=False) + username = forms.CharField(max_length=100, label=_("Username"), required=False) - email = forms.CharField(max_length=100, label=_('E-Mail address'), required=False) + email = forms.CharField(max_length=100, label=_("E-Mail address"), required=False) diff --git a/mygpo/web/google.py b/mygpo/web/google.py index 20b345c1b..d0757e804 100644 --- a/mygpo/web/google.py +++ b/mygpo/web/google.py @@ -4,7 +4,7 @@ def analytics(request): pid = settings.GOOGLE_ANALYTICS_PROPERTY_ID if pid: - return {'google_analytics_property_id': pid} + return {"google_analytics_property_id": pid} else: return {} @@ -18,4 +18,4 @@ def adsense(request): if not slot_bottom: return {} - return {'adsense_client': adclient, 'adsense_slot_bottom': slot_bottom} + return {"adsense_client": adclient, "adsense_slot_bottom": slot_bottom} diff --git a/mygpo/web/logo.py b/mygpo/web/logo.py index 7ec08a8fc..b5bb71986 100644 --- a/mygpo/web/logo.py +++ b/mygpo/web/logo.py @@ -30,7 +30,7 @@ def _last_modified(request, size, prefix, filename): - target = os.path.join('logo', str(size), prefix, filename) + target = os.path.join("logo", str(size), prefix, filename) try: return LOGO_STORAGE.get_modified_time(target) @@ -56,25 +56,25 @@ def get(self, request, size, prefix, filename): return self.send_file(target) if not self.storage.exists(original): - logger.warning('Original cover {} not found'.format(original)) - raise Http404('Cover Art not available' + original) + logger.warning("Original cover {} not found".format(original)) + raise Http404("Cover Art not available" + original) target_dir = self.get_dir(filename) try: - fp = self.storage.open(original, 'rb') + fp = self.storage.open(original, "rb") im = Image.open(fp) - if im.mode not in ('RGB', 'RGBA'): - im = im.convert('RGBA') + if im.mode not in ("RGB", "RGBA"): + im = im.convert("RGBA") except IOError as ioe: - logger.warning('Cover file {} cannot be opened: {}'.format(original, ioe)) - raise Http404('Cannot open cover file') from ioe + logger.warning("Cover file {} cannot be opened: {}".format(original, ioe)) + raise Http404("Cannot open cover file") from ioe try: im.thumbnail((size, size), Image.ANTIALIAS) resized = im except (struct.error, IOError, IndexError) as ex: # raised when trying to read an interlaced PNG; - logger.warning('Could not create thumbnail: %s', str(ex)) + logger.warning("Could not create thumbnail: %s", str(ex)) # we use the original instead return self.send_file(original) @@ -82,7 +82,7 @@ def get(self, request, size, prefix, filename): sio = io.BytesIO() try: - resized.save(sio, 'JPEG', optimize=True, progression=True, quality=80) + resized.save(sio, "JPEG", optimize=True, progression=True, quality=80) except IOError as ex: return self.send_file(original) finally: @@ -94,7 +94,7 @@ def get(self, request, size, prefix, filename): @staticmethod def get_thumbnail_path(size, prefix, filename): - return os.path.join('logo', str(size), prefix, filename) + return os.path.join("logo", str(size), prefix, filename) @staticmethod def get_dir(filename): @@ -102,18 +102,18 @@ def get_dir(filename): @staticmethod def remove_existing_thumbnails(prefix, filename): - dirs, _files = LOGO_STORAGE.listdir('logo') # TODO: cache list of sizes + dirs, _files = LOGO_STORAGE.listdir("logo") # TODO: cache list of sizes for size in dirs: - if size == 'original': + if size == "original": continue - path = os.path.join('logo', size, prefix, filename) - logger.info('Removing {}'.format(path)) + path = os.path.join("logo", size, prefix, filename) + logger.info("Removing {}".format(path)) LOGO_STORAGE.delete(path) @staticmethod def get_original_path(prefix, filename): - return os.path.join('logo', 'original', prefix, filename) + return os.path.join("logo", "original", prefix, filename) def send_file(self, filename): return HttpResponseRedirect(LOGO_STORAGE.url(filename)) @@ -124,7 +124,7 @@ def save_podcast_logo(cls, cover_art_url): return try: - image_sha1 = hashlib.sha1(cover_art_url.encode('utf-8')).hexdigest() + image_sha1 = hashlib.sha1(cover_art_url.encode("utf-8")).hexdigest() prefix = get_prefix(image_sha1) filename = cls.get_original_path(prefix, image_sha1) @@ -132,12 +132,12 @@ def save_podcast_logo(cls, cover_art_url): # get hash of existing file if LOGO_STORAGE.exists(filename): - with LOGO_STORAGE.open(filename, 'rb') as f: + with LOGO_STORAGE.open(filename, "rb") as f: old_hash = file_hash(f).digest() else: - old_hash = '' + old_hash = "" - logger.info('Logo {}, saving to {}'.format(cover_art_url, filename)) + logger.info("Logo {}, saving to {}".format(cover_art_url, filename)) # save new cover art LOGO_STORAGE.delete(filename) @@ -145,12 +145,12 @@ def save_podcast_logo(cls, cover_art_url): LOGO_STORAGE.save(filename, source) # get hash of new file - with LOGO_STORAGE.open(filename, 'rb') as f: + with LOGO_STORAGE.open(filename, "rb") as f: new_hash = file_hash(f).digest() # remove thumbnails if cover changed if old_hash != new_hash: - logger.info('Removing thumbnails') + logger.info("Removing thumbnails") thumbnails = cls.remove_existing_thumbnails(prefix, filename) return cover_art_url @@ -161,7 +161,7 @@ def save_podcast_logo(cls, cover_art_url): socket.error, IOError, ) as e: - logger.warning('Exception while updating podcast logo: %s', str(e)) + logger.warning("Exception while updating podcast logo: %s", str(e)) def get_prefix(filename): @@ -169,16 +169,16 @@ def get_prefix(filename): def get_logo_url(podcast, size): - """ Return the logo URL for the podcast + """Return the logo URL for the podcast The logo either comes from the media storage (see CoverArt) or from the default logos in the static storage. """ if podcast.logo_url: - filename = hashlib.sha1(podcast.logo_url.encode('utf-8')).hexdigest() - return reverse('logo', args=[size, get_prefix(filename), filename]) + filename = hashlib.sha1(podcast.logo_url.encode("utf-8")).hexdigest() + return reverse("logo", args=[size, get_prefix(filename), filename]) else: - filename = 'podcast-%d.png' % (hash(podcast.title) % 5,) - return staticfiles_storage.url('logo/{0}'.format(filename)) + filename = "podcast-%d.png" % (hash(podcast.title) % 5,) + return staticfiles_storage.url("logo/{0}".format(filename)) diff --git a/mygpo/web/templatetags/charts.py b/mygpo/web/templatetags/charts.py index 3e0eec6a7..774b742f6 100644 --- a/mygpo/web/templatetags/charts.py +++ b/mygpo/web/templatetags/charts.py @@ -11,10 +11,10 @@ @register.simple_tag def vertical_bar(value, max_value, display=None): if not max_value: - return '' + return "" - if display == 'ratio': - value_str = '%d/%d' % (value, max_value) + if display == "ratio": + value_str = "%d/%d" % (value, max_value) else: value_str = str(value) @@ -24,14 +24,14 @@ def vertical_bar(value, max_value, display=None): try: ratio = min(float(value) / float(max_value), 1) * 100 except ValueError: - return '' + return "" if ratio > 40: - left = format_html('{}', value_str) - right = '' + left = format_html("{}", value_str) + right = "" else: - left = format_html(' ') - right = format_html('{}', value_str) + left = format_html(" ") + right = format_html("{}", value_str) return format_html( '
' % (_("The episode has been flattr'd"),) ) - elif action.action == 'new': + elif action.action == "new": s = 'new' % ( - staticfiles_storage.url('new.png'), - '%s%s%s' - % (_('This episode has been marked new'), date_string, device_string), + staticfiles_storage.url("new.png"), + "%s%s%s" + % (_("This episode has been marked new"), date_string, device_string), ) - elif action.action == 'download': + elif action.action == "download": s = 'downloaded' % ( - staticfiles_storage.url('download.png'), - '%s%s%s' - % (_('This episode has been downloaded'), date_string, device_string), + staticfiles_storage.url("download.png"), + "%s%s%s" + % (_("This episode has been downloaded"), date_string, device_string), ) - elif action.action == 'play': + elif action.action == "play": if action.stopped is not None: - if getattr(action, 'started', None) is not None: - playback_info = _(' from %(start)s to %(end)s') % { - 'start': utils.format_time(action.started), - 'end': utils.format_time(action.stopped), + if getattr(action, "started", None) is not None: + playback_info = _(" from %(start)s to %(end)s") % { + "start": utils.format_time(action.started), + "end": utils.format_time(action.stopped), } else: - playback_info = _(' to position %s') % ( + playback_info = _(" to position %s") % ( utils.format_time(action.stopped), ) else: - playback_info = '' + playback_info = "" s = 'played' % ( - staticfiles_storage.url('playback.png'), - '%s%s%s%s' + staticfiles_storage.url("playback.png"), + "%s%s%s%s" % ( - _('This episode has been played'), + _("This episode has been played"), date_string, device_string, playback_info, ), ) - elif action.action == 'delete': + elif action.action == "delete": s = 'deleted' % ( - staticfiles_storage.url('delete.png'), - '%s%s%s' - % (_('This episode has been deleted'), date_string, device_string), + staticfiles_storage.url("delete.png"), + "%s%s%s" + % (_("This episode has been deleted"), date_string, device_string), ) else: return action.action # this is not marked safe by intention @@ -105,17 +105,17 @@ def episode_status_icon(action): @register.filter def is_image(episode): - mimetypes = episode.mimetypes.split(',') - return any(get_type(mimetype) == 'image' for mimetype in mimetypes) + mimetypes = episode.mimetypes.split(",") + return any(get_type(mimetype) == "image" for mimetype in mimetypes) class EpisodeLinkTargetNode(template.Node): """ Links to a (view of a) Podcast """ - def __init__(self, episode, podcast, view_name='episode', add_args=[]): + def __init__(self, episode, podcast, view_name="episode", add_args=[]): self.episode = template.Variable(episode) self.podcast = template.Variable(podcast) - self.view_name = view_name.replace('"', '') + self.view_name = view_name.replace('"', "") self.add_args = [template.Variable(arg) for arg in add_args] def render(self, context): @@ -131,7 +131,7 @@ def compile(parser, token): tag_name = contents[0] episode = contents[1] podcast = contents[2] - view_name = contents[3] if len(contents) > 3 else 'episode' + view_name = contents[3] if len(contents) > 3 else "episode" add_args = contents[4:] except ValueError: @@ -142,7 +142,7 @@ def compile(parser, token): return EpisodeLinkTargetNode(episode, podcast, view_name, add_args) -register.tag('episode_link_target', EpisodeLinkTargetNode.compile) +register.tag("episode_link_target", EpisodeLinkTargetNode.compile) @register.simple_tag @@ -151,10 +151,10 @@ def episode_link(episode, podcast, title=None): title = ( title - or getattr(episode, 'display_title', None) + or getattr(episode, "display_title", None) or episode.get_short_title(podcast.common_episode_title) or episode.title - or _('Unknown Episode') + or _("Unknown Episode") ) title = strip_tags(title) diff --git a/mygpo/web/templatetags/facebook.py b/mygpo/web/templatetags/facebook.py index 139cd4473..31d5d9ee3 100644 --- a/mygpo/web/templatetags/facebook.py +++ b/mygpo/web/templatetags/facebook.py @@ -24,7 +24,7 @@ @register.simple_tag @mark_safe def fb_like_episode(episode, podcast): - url = 'http://gpodder.net/%s' % get_episode_link_target(episode, podcast) + url = "http://gpodder.net/%s" % get_episode_link_target(episode, podcast) s = LIKE_BUTTON_STR % dict(url=url) return s @@ -32,7 +32,7 @@ def fb_like_episode(episode, podcast): @register.filter @mark_safe def fb_like_podcast(podcast): - url = 'http://gpodder.net%s' % get_podcast_link_target(podcast) + url = "http://gpodder.net%s" % get_podcast_link_target(podcast) s = LIKE_BUTTON_STR % dict(url=url) return s @@ -51,11 +51,11 @@ def fb_like_podcast(podcast): def opengraph_episode(episode, podcast): s = OPENGRAPH_STR % dict( title=episode.title, - type='episode', - image='http://gpodder.net%s' % get_logo_url(podcast, PODCAST_LOGO_BIG_SIZE), - url='http://gpodder.net%s' % get_episode_link_target(episode, podcast), - site_name='gpodder.net', - admins='0', + type="episode", + image="http://gpodder.net%s" % get_logo_url(podcast, PODCAST_LOGO_BIG_SIZE), + url="http://gpodder.net%s" % get_episode_link_target(episode, podcast), + site_name="gpodder.net", + admins="0", ) return s @@ -64,10 +64,10 @@ def opengraph_episode(episode, podcast): def opengraph_podcast(podcast): s = OPENGRAPH_STR % dict( title=podcast.title, - type='episode', - image='http://gpodder.net%s' % get_logo_url(podcast, PODCAST_LOGO_BIG_SIZE), - url='http://gpodder.net%s' % get_podcast_link_target(podcast), - site_name='gpodder.net', - admins='0', + type="episode", + image="http://gpodder.net%s" % get_logo_url(podcast, PODCAST_LOGO_BIG_SIZE), + url="http://gpodder.net%s" % get_podcast_link_target(podcast), + site_name="gpodder.net", + admins="0", ) return mark_safe(s) diff --git a/mygpo/web/templatetags/menu.py b/mygpo/web/templatetags/menu.py index 2856d8874..a2c2106cd 100644 --- a/mygpo/web/templatetags/menu.py +++ b/mygpo/web/templatetags/menu.py @@ -7,75 +7,75 @@ register = template.Library() HIDDEN_URIS = ( - '/login/', - '/register/', - '/podcast/', - '/device/', - '/user/subscriptions/', - '/publisher/podcast/', - '/share/me', + "/login/", + "/register/", + "/podcast/", + "/device/", + "/user/subscriptions/", + "/publisher/podcast/", + "/share/me", ) MENU_STRUCTURE = ( ( - '', + "", ( - ('/', _('Home')), - ('/login/', _('Login')), - ('/register/', _('Register')), - ('', _('Docs')), - ('/contribute/', _('Contribute')), - ('/developer/', _('Development')), - ('/privacy/', _('Privacy Policy')), - ('/online-help', _('Help')), + ("/", _("Home")), + ("/login/", _("Login")), + ("/register/", _("Register")), + ("", _("Docs")), + ("/contribute/", _("Contribute")), + ("/developer/", _("Development")), + ("/privacy/", _("Privacy Policy")), + ("/online-help", _("Help")), ), ), ( - _('Discover'), + _("Discover"), ( - ('/directory/', _('Directory')), - ('/podcast/', _('Podcast')), - ('/search/', _('Search')), - ('/missing/', _('Missing Podcast')), - ('/lists/', _('Podcast Lists')), - ('/user/subscriptions/', _('User subscriptions')), - ('/suggestions/', _('Suggestions')), - ('', _('Features')), - ('/directory/+license', _('License')), - ('', _('Toplists')), - ('/toplist/', _('Podcasts')), - ('/toplist/episodes', _('Episodes')), + ("/directory/", _("Directory")), + ("/podcast/", _("Podcast")), + ("/search/", _("Search")), + ("/missing/", _("Missing Podcast")), + ("/lists/", _("Podcast Lists")), + ("/user/subscriptions/", _("User subscriptions")), + ("/suggestions/", _("Suggestions")), + ("", _("Features")), + ("/directory/+license", _("License")), + ("", _("Toplists")), + ("/toplist/", _("Podcasts")), + ("/toplist/episodes", _("Episodes")), ), ), ( - _('Subscriptions'), + _("Subscriptions"), ( - ('/subscriptions/', _('Subscriptions')), - ('/favorites/', _('Favorite Episodes')), - ('/tags/', _('My Tags')), - ('/devices/', _('Devices')), - ('/device/', _('Device')), - ('/history/', _('History')), + ("/subscriptions/", _("Subscriptions")), + ("/favorites/", _("Favorite Episodes")), + ("/tags/", _("My Tags")), + ("/devices/", _("Devices")), + ("/device/", _("Device")), + ("/history/", _("History")), ), ), ( - _('Community'), + _("Community"), ( - ('/share/', _('Overview')), - ('/share/favorites', _('Favorite Episodes')), - ('/share/me', _('My Userpage')), - ('/user/subscriptions/', _('Subscriptions')), - ('/share/lists/', _('Podcast Lists')), + ("/share/", _("Overview")), + ("/share/favorites", _("Favorite Episodes")), + ("/share/me", _("My Userpage")), + ("/user/subscriptions/", _("Subscriptions")), + ("/share/lists/", _("Podcast Lists")), ), ), - (_('Settings'), (('/account/', _('Account')), ('/account/privacy', _('Privacy')))), + (_("Settings"), (("/account/", _("Account")), ("/account/privacy", _("Privacy")))), ( - _('Publish'), + _("Publish"), ( - ('/publisher/', _('Home')), - ('/publisher/advertise', _('Advertise')), - ('/publisher/link/', _('Link to gpodder.net')), - ('/publisher/podcast/', _('Podcast')), + ("/publisher/", _("Home")), + ("/publisher/advertise", _("Advertise")), + ("/publisher/link/", _("Link to gpodder.net")), + ("/publisher/podcast/", _("Podcast")), ), ), ) @@ -93,14 +93,14 @@ def main_menu(selected): items = [] for uri, caption, subpages in links: - if selected in subpages or ('/' in subpages and not found_section): + if selected in subpages or ("/" in subpages and not found_section): items.append( '
  • %s
  • ' % (uri, gettext(caption)) ) else: items.append('
  • %s
  • ' % (uri, gettext(caption))) - return mark_safe('\n'.join(items)) + return mark_safe("\n".join(items)) def get_section_items(selected): @@ -120,13 +120,13 @@ def section_menu(selected, title=None): if uri == selected: if title is not None: if len(title) > 35: - title = title[:33] + '...' + title = title[:33] + "..." caption = title if uri in HIDDEN_URIS: items.append( '
  • %s
  • ' % gettext(caption) ) - elif uri == '': + elif uri == "": items.append( '' % gettext(caption) @@ -147,4 +147,4 @@ def section_menu(selected, title=None): else: items.append('
  • %s
  • ' % (uri, gettext(caption))) - return mark_safe('\n'.join(items)) + return mark_safe("\n".join(items)) diff --git a/mygpo/web/templatetags/mygpoutil.py b/mygpo/web/templatetags/mygpoutil.py index 808590a7b..c3e182bed 100644 --- a/mygpo/web/templatetags/mygpoutil.py +++ b/mygpo/web/templatetags/mygpoutil.py @@ -12,31 +12,31 @@ @register.filter() def remove_html_tags(html): # If we would want more speed, we could make these global - re_strip_tags = re.compile('<[^>]*>') - re_unicode_entities = re.compile(r'&#(\d{2,4});') - re_html_entities = re.compile('&(.{2,8});') - re_newline_tags = re.compile('(]*>|<[/]?ul[^>]*>|)', re.I) - re_listing_tags = re.compile(']*>', re.I) + re_strip_tags = re.compile("<[^>]*>") + re_unicode_entities = re.compile(r"&#(\d{2,4});") + re_html_entities = re.compile("&(.{2,8});") + re_newline_tags = re.compile("(]*>|<[/]?ul[^>]*>|)", re.I) + re_listing_tags = re.compile("]*>", re.I) result = html # Convert common HTML elements to their text equivalent - result = re_newline_tags.sub('\n', result) - result = re_listing_tags.sub('\n * ', result) - result = re.sub('<[Pp]>', '\n\n', result) + result = re_newline_tags.sub("\n", result) + result = re_listing_tags.sub("\n * ", result) + result = re.sub("<[Pp]>", "\n\n", result) # Remove all HTML/XML tags from the string - result = re_strip_tags.sub('', result) + result = re_strip_tags.sub("", result) # Convert numeric XML entities to their unicode character result = re_unicode_entities.sub(lambda x: chr(int(x.group(1))), result) # Convert named HTML entities to their unicode character result = re_html_entities.sub( - lambda x: str(entitydefs.get(x.group(1), ''), 'iso-8859-1'), result + lambda x: str(entitydefs.get(x.group(1), ""), "iso-8859-1"), result ) # Convert more than two newlines to two newlines - result = re.sub('([\r\n]{2})([\r\n])+', '\\1', result) + result = re.sub("([\r\n]{2})([\r\n])+", "\\1", result) return mark_safe(result.strip()) diff --git a/mygpo/web/templatetags/podcasts.py b/mygpo/web/templatetags/podcasts.py index a66e5f68c..2339a89dd 100644 --- a/mygpo/web/templatetags/podcasts.py +++ b/mygpo/web/templatetags/podcasts.py @@ -21,7 +21,7 @@ @mark_safe def create_podcast_logo(podcast, size): if not podcast: - return '' + return "" size = int(size) return '' % (get_logo_url(podcast, size),) @@ -44,15 +44,15 @@ def podcast_logo_medium(podcast): @register.filter() def podcast_status_icon(action): - s = '' - if action.action == 'subscribe': - s = '' % (staticfiles_storage.url('subscribe.png'),) - elif action.action == 'unsubscribe': - s = '' % (staticfiles_storage.url('unsubscribe.png'),) - elif action.action == 'flattr': + s = "" + if action.action == "subscribe": + s = '' % (staticfiles_storage.url("subscribe.png"),) + elif action.action == "unsubscribe": + s = '' % (staticfiles_storage.url("unsubscribe.png"),) + elif action.action == "flattr": s = '' else: - s = '' + s = "" return mark_safe(s) @@ -70,7 +70,7 @@ class PodcastLinkTargetNode(template.Node): def __init__(self, podcast, view_name, add_args): self.podcast = template.Variable(podcast) - self.view_name = view_name.replace('"', '') + self.view_name = view_name.replace('"', "") self.add_args = [template.Variable(arg) for arg in add_args] def render(self, context): @@ -84,7 +84,7 @@ def compile(parser, token): contents = token.split_contents() tag_name = contents[0] podcast = contents[1] - view_name = contents[2] if len(contents) > 2 else 'podcast' + view_name = contents[2] if len(contents) > 2 else "podcast" add_args = contents[3:] except ValueError: @@ -95,7 +95,7 @@ def compile(parser, token): return PodcastLinkTargetNode(podcast, view_name, add_args) -register.tag('podcast_link_target', PodcastLinkTargetNode.compile) +register.tag("podcast_link_target", PodcastLinkTargetNode.compile) class PodcastGroupLinkTargetNode(template.Node): @@ -103,7 +103,7 @@ class PodcastGroupLinkTargetNode(template.Node): def __init__(self, group, view_name, add_args): self.group = template.Variable(group) - self.view_name = view_name.replace('"', '') + self.view_name = view_name.replace('"', "") self.add_args = [template.Variable(arg) for arg in add_args] def render(self, context): @@ -117,7 +117,7 @@ def compile(parser, token): contents = token.split_contents() tag_name = contents[0] podcast = contents[1] - view_name = contents[2] if len(contents) > 2 else 'podcast' + view_name = contents[2] if len(contents) > 2 else "podcast" add_args = contents[3:] except ValueError: @@ -128,14 +128,14 @@ def compile(parser, token): return PodcastLinkTargetNode(podcast, view_name, add_args) -register.tag('podcast_group_link_target', PodcastGroupLinkTargetNode.compile) +register.tag("podcast_group_link_target", PodcastGroupLinkTargetNode.compile) @register.simple_tag def podcast_group_link(podcast, title=None): - """ Returns the link strings for Podcast and PodcastGroup objects + """Returns the link strings for Podcast and PodcastGroup objects - automatically distinguishes between relational Podcast/PodcastGroup """ + automatically distinguishes between relational Podcast/PodcastGroup""" from mygpo.podcasts.models import PodcastGroup @@ -145,8 +145,8 @@ def podcast_group_link(podcast, title=None): return podcast_link(podcast, title) links = (podcast_link(p, p.group_member_name) for p in podcasts) - link_text = ' '.join(links) - return '%(title)s (%(links)s)' % dict(title=podcast.title, links=link_text) + link_text = " ".join(links) + return "%(title)s (%(links)s)" % dict(title=podcast.title, links=link_text) @register.simple_tag diff --git a/mygpo/web/templatetags/time.py b/mygpo/web/templatetags/time.py index ec69ccaa1..7d23a96f0 100644 --- a/mygpo/web/templatetags/time.py +++ b/mygpo/web/templatetags/time.py @@ -10,7 +10,7 @@ @register.filter def sec_to_time(sec): - """ Converts seconds to a time object + """Converts seconds to a time object >>> t = sec_to_time(1000) >>> (t.hour, t.minute, t.second) @@ -26,7 +26,7 @@ def sec_to_time(sec): @register.filter def format_duration(sec): - """ Converts seconds into a duration string + """Converts seconds into a duration string >>> format_duration(1000) '16m 40s' @@ -39,7 +39,7 @@ def format_duration(sec): seconds = int(sec % 60) if hours: - s = _('{h}h {m}m {s}s').format(h=hours, m=minutes, s=seconds) + s = _("{h}h {m}m {s}s").format(h=hours, m=minutes, s=seconds) else: - s = _('{m}m {s}s').format(m=minutes, s=seconds) + s = _("{m}m {s}s").format(m=minutes, s=seconds) return mark_safe(s) diff --git a/mygpo/web/templatetags/utils.py b/mygpo/web/templatetags/utils.py index d8add2c0a..f0a0dae37 100644 --- a/mygpo/web/templatetags/utils.py +++ b/mygpo/web/templatetags/utils.py @@ -13,7 +13,7 @@ @register.filter() def lookup(dic, key): - return dic.get(key, '') + return dic.get(key, "") @register.filter @@ -66,7 +66,7 @@ def is_tuple(obj): def markdown(txt): import markdown2 - return mark_safe(markdown2.markdown(txt, extras={'nofollow': True})) + return mark_safe(markdown2.markdown(txt, extras={"nofollow": True})) @register.filter() @@ -84,7 +84,7 @@ def license_name(license_url): info = license_info(license_url) if info.name: - return '%s %s' % (info.name, info.version or '') + return "%s %s" % (info.name, info.version or "") return info.url @@ -93,7 +93,7 @@ def license_name(license_url): def urlquote(s): """ makes urllib.quote_plus available as a template filter """ if isinstance(s, str): - s = s.encode('utf-8') + s = s.encode("utf-8") return mark_safe(urllib.parse.quote_plus(s)) @@ -102,7 +102,7 @@ def urlquote(s): @register.simple_tag def protocol(request): - return 'http{s}://'.format(s='s' if request.is_secure() else '') + return "http{s}://".format(s="s" if request.is_secure() else "") edit_link = register.simple_tag(edit_link) diff --git a/mygpo/web/tests.py b/mygpo/web/tests.py index 3a64ecd84..ff5b1563c 100644 --- a/mygpo/web/tests.py +++ b/mygpo/web/tests.py @@ -23,11 +23,11 @@ IMG_PATH1 = os.path.abspath( - os.path.join(settings.BASE_DIR, '..', 'res', 'gpoddernet_228.png') + os.path.join(settings.BASE_DIR, "..", "res", "gpoddernet_228.png") ) IMG_PATH2 = os.path.abspath( - os.path.join(settings.BASE_DIR, '..', 'res', 'gpoddernet_16.png') + os.path.join(settings.BASE_DIR, "..", "res", "gpoddernet_16.png") ) @@ -35,11 +35,11 @@ class SimpleWebTests(TestCase): @classmethod def setUpClass(self): User = get_user_model() - self.user = User(username='web-test', email='web-test@example.com') - self.user.set_password('pwd') + self.user = User(username="web-test", email="web-test@example.com") + self.user.set_password("pwd") self.user.save() - self.auth_string = create_auth_string('test', 'pwd') + self.auth_string = create_auth_string("test", "pwd") @classmethod def tearDownClass(self): @@ -47,34 +47,34 @@ def tearDownClass(self): def test_access_parameterless_pages(self): pages = [ - 'history', - 'suggestions', - 'tags', - 'subscriptions', - 'subscriptions-opml', - 'favorites', - 'account', - 'privacy', - 'delete-account', - 'share', - 'toplist', - 'episode-toplist', - 'devices', - 'device-create', - 'login', - 'logout', - 'home', + "history", + "suggestions", + "tags", + "subscriptions", + "subscriptions-opml", + "favorites", + "account", + "privacy", + "delete-account", + "share", + "toplist", + "episode-toplist", + "devices", + "device-create", + "login", + "logout", + "home", ] self.access_pages(pages, [], True) def test_access_podcast_pages(self): - pages = ['podcast'] + pages = ["podcast"] def access_pages(self, pages, args, login): if login: self.client.post( - '/login/', dict(login_username=self.user.username, pwd='pwd') + "/login/", dict(login_username=self.user.username, pwd="pwd") ) for page in pages: @@ -88,11 +88,11 @@ class PodcastPageTests(TestCase): def setUp(self): # create a podcast and some episodes podcast = Podcast.objects.create( - id=uuid.uuid1(), title='My Podcast', max_episode_order=1 + id=uuid.uuid1(), title="My Podcast", max_episode_order=1 ) for n in range(20): episode = Episode.objects.get_or_create_for_url( - podcast, 'http://www.example.com/episode%d.mp3' % (n,) + podcast, "http://www.example.com/episode%d.mp3" % (n,) ).object # we only need (the last) one @@ -101,12 +101,12 @@ def setUp(self): ) self.podcast_slug = Slug.objects.create( - content_object=podcast, order=n, scope=podcast.scope, slug='podcast' + content_object=podcast, order=n, scope=podcast.scope, slug="podcast" ) def test_podcast_queries(self): """ Test that the expected number of queries is executed """ - url = reverse('podcast-slug', args=(self.podcast_slug.slug,)) + url = reverse("podcast-slug", args=(self.podcast_slug.slug,)) # the number of queries must be independent of the number of episodes with self.assertNumQueries(5): @@ -115,7 +115,7 @@ def test_podcast_queries(self): def test_episode_queries(self): """ Test that the expected number of queries is executed """ url = reverse( - 'episode-slug', args=(self.podcast_slug.slug, self.episode_slug.slug) + "episode-slug", args=(self.podcast_slug.slug, self.episode_slug.slug) ) with self.assertNumQueries(5): @@ -125,9 +125,9 @@ def test_episode_queries(self): class PodcastLogoTests(TestCase): def setUp(self): # create a podcast - self.URL = 'http://example.com/{}.png'.format(uuid.uuid1().hex) + self.URL = "http://example.com/{}.png".format(uuid.uuid1().hex) self.podcast = Podcast.objects.create( - id=uuid.uuid1(), title='My Podcast', max_episode_order=1, logo_url=self.URL + id=uuid.uuid1(), title="My Podcast", max_episode_order=1, logo_url=self.URL ) self.client = Client() @@ -135,9 +135,9 @@ def tearDown(self): self.podcast.delete() def _save_logo(self): - with responses.RequestsMock() as rsps, open(IMG_PATH1, 'rb') as body: + with responses.RequestsMock() as rsps, open(IMG_PATH1, "rb") as body: rsps.add( - responses.GET, self.URL, status=200, body=body, content_type='image/png' + responses.GET, self.URL, status=200, body=body, content_type="image/png" ) CoverArt.save_podcast_logo(self.URL) @@ -147,9 +147,9 @@ def _fetch_cover(self, podcast, size=32): response = self.client.get(logo_url) self.assertEqual(302, response.status_code) - redir = response['Location'] + redir = response["Location"] - logger.warning('Redirecting to {}'.format(redir)) + logger.warning("Redirecting to {}".format(redir)) response = self.client.get(redir) self.assertEqual(200, response.status_code) @@ -160,7 +160,7 @@ def test_save_logo(self): self._fetch_cover(self.podcast) def test_get_nonexisting(self): - URL = 'http://example.com/non-existing-logo.png' + URL = "http://example.com/non-existing-logo.png" self.podcast.logo_url = URL @@ -170,10 +170,10 @@ def test_get_nonexisting(self): self.assertEqual(404, response.status_code) def test_get_existing_thumbnail(self): - """ Retrieve an already existing thumbnail + """Retrieve an already existing thumbnail No distinction is visible outside, but it covers different - code paths """ + code paths""" self._save_logo() logo_url = get_logo_url(self.podcast, 32) @@ -190,7 +190,7 @@ def test_save_empty_logo(self): CoverArt.save_podcast_logo(None) except: self.fail( - 'CoverArt.save_podcast_logo(None) should not raise ' 'an exception' + "CoverArt.save_podcast_logo(None) should not raise " "an exception" ) def test_exception_during_fetch(self): @@ -198,7 +198,7 @@ def test_exception_during_fetch(self): rsps.add( responses.GET, self.URL, - body=requests.exceptions.RequestException('Fetching URL failed'), + body=requests.exceptions.RequestException("Fetching URL failed"), ) CoverArt.save_podcast_logo(self.URL) @@ -222,29 +222,29 @@ def open(*args, **kwargs): logo.LOGO_STORAGE = _logo_storage def test_new_logo(self): - with responses.RequestsMock() as rsps, open(IMG_PATH1, 'rb') as body1, open( - IMG_PATH1, 'rb' - ) as body2, open(IMG_PATH2, 'rb') as body3: + with responses.RequestsMock() as rsps, open(IMG_PATH1, "rb") as body1, open( + IMG_PATH1, "rb" + ) as body2, open(IMG_PATH2, "rb") as body3: rsps.add( responses.GET, self.URL, status=200, body=body1, - content_type='image/png', + content_type="image/png", ) rsps.add( responses.GET, self.URL, status=200, body=body2, - content_type='image/png', + content_type="image/png", ) rsps.add( responses.GET, self.URL, status=200, body=body3, - content_type='image/png', + content_type="image/png", ) logo_url = get_logo_url(self.podcast, 32) @@ -268,3 +268,43 @@ def test_new_logo(self): self.assertNotEqual( list(response2.streaming_content), list(response3.streaming_content) ) + + +class PublisherPageTests(TestCase): + """ Test the publisher page """ + + @classmethod + def setUpTestData(self): + User = get_user_model() + self.user = User(username="web-test", email="web-test@example.com") + self.user.set_password("pwd") + self.user.is_staff = True + self.user.save() + + def test_publisher_detail_slug(self): + # create a podcast with slug + podcast = Podcast.objects.get_or_create_for_url( + "http://example.com/podcast.rss" + ).object + slug = "test" + podcast.set_slug(slug) + + url = reverse("podcast-publisher-detail-slug", args=(slug,)) + + self.client.login(username="web-test", password="pwd") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_publisher_detail_id(self): + # create a podcast with no slug + podcast = Podcast.objects.get_or_create_for_url( + "http://example.com/podcast2.rss" + ).object + + url = reverse("podcast-publisher-detail-id", args=(podcast.id,)) + + self.client.login(username="web-test", password="pwd") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/mygpo/web/urls.py b/mygpo/web/urls.py index 8ea234463..f8fabdba5 100644 --- a/mygpo/web/urls.py +++ b/mygpo/web/urls.py @@ -11,36 +11,36 @@ urlpatterns = [ - path('', views.home, name='home'), + path("", views.home, name="home"), path( - 'logo///', CoverArt.as_view(), name='logo' + "logo///", CoverArt.as_view(), name="logo" ), # Media files are also served in production mode. For performance, these # files should be served by a reverse proxy in practice path( - '%s' % settings.MEDIA_URL.lstrip('/'), + "%s" % settings.MEDIA_URL.lstrip("/"), serve, - name='media', + name="media", kwargs=dict(document_root=settings.MEDIA_ROOT), ), - path('tags/', views.mytags, name='tags'), + path("tags/", views.mytags, name="tags"), path( - 'online-help', + "online-help", RedirectView.as_view( - url='http://gpoddernet.readthedocs.org/en/latest/user/index.html', + url="http://gpoddernet.readthedocs.org/en/latest/user/index.html", permanent=False, ), - name='help', + name="help", ), - path('developer/', TemplateView.as_view(template_name='developer.html')), + path("developer/", TemplateView.as_view(template_name="developer.html")), path( - 'contribute/', - TemplateView.as_view(template_name='contribute.html'), - name='contribute', + "contribute/", + TemplateView.as_view(template_name="contribute.html"), + name="contribute", ), path( - 'privacy/', - TemplateView.as_view(template_name='privacy_policy.html'), - name='privacy-policy', + "privacy/", + TemplateView.as_view(template_name="privacy_policy.html"), + name="privacy-policy", ), ] diff --git a/mygpo/web/utils.py b/mygpo/web/utils.py index c99b6bdaa..3701cedd7 100644 --- a/mygpo/web/utils.py +++ b/mygpo/web/utils.py @@ -19,16 +19,16 @@ def get_accepted_lang(request): """ returns a list of language codes accepted by the HTTP request """ - lang_str = request.META.get('HTTP_ACCEPT_LANGUAGE', '') - lang_str = ''.join([c for c in lang_str if c in string.ascii_letters + ',']) - langs = lang_str.split(',') + lang_str = request.META.get("HTTP_ACCEPT_LANGUAGE", "") + lang_str = "".join([c for c in lang_str if c in string.ascii_letters + ","]) + langs = lang_str.split(",") langs = [s[:2] for s in langs] langs = list(map(str.strip, langs)) langs = [_f for _f in langs if _f] return list(set(langs)) -RE_LANG = re.compile('^[a-zA-Z]{2}[-_]?.*$') +RE_LANG = re.compile("^[a-zA-Z]{2}[-_]?.*$") def sanitize_language_code(lang): @@ -99,7 +99,7 @@ def get_page_list(start, total, cur, show_max): ps = [] if (cur - start) > show_max / 2: ps.extend(list(range(start, int(show_max / 4)))) - ps.append('...') + ps.append("...") ps.extend(list(range(cur - int(show_max / 4), cur))) else: @@ -111,7 +111,7 @@ def get_page_list(start, total, cur, show_max): # for the first pages, show more pages at the beginning add = math.ceil(show_max / 2 - len(ps)) ps.extend(list(range(cur + 1, cur + int(show_max / 4) + add))) - ps.append('...') + ps.append("...") ps.extend(list(range(total - int(show_max / 4), total + 1))) else: @@ -122,39 +122,39 @@ def get_page_list(start, total, cur, show_max): def process_lang_params(request): - lang = request.GET.get('lang', None) + lang = request.GET.get("lang", None) if lang is None: langs = get_accepted_lang(request) - lang = next(iter(langs), '') + lang = next(iter(langs), "") return sanitize_language_code(lang) def symbian_opml_changes(podcast): - podcast.description = podcast.display_title + '\n' + (podcast.description or '') + podcast.description = podcast.display_title + "\n" + (podcast.description or "") return podcast @never_cache def maintenance(request, *args, **kwargs): - resp = render(request, 'maintenance.html', {}) + resp = render(request, "maintenance.html", {}) resp.status_code = 503 return resp -def get_podcast_link_target(podcast, view_name='podcast', add_args=[]): +def get_podcast_link_target(podcast, view_name="podcast", add_args=[]): """ Returns the link-target for a Podcast, preferring slugs over Ids """ # we prefer slugs if podcast.slug: args = [podcast.slug] - view_name = '%s-slug' % view_name + view_name = "%s-slug" % view_name # as a fallback we use UUIDs else: args = [podcast.id] - view_name = '%s-id' % view_name + view_name = "%s-id" % view_name return reverse(view_name, args=args + add_args) @@ -162,29 +162,29 @@ def get_podcast_link_target(podcast, view_name='podcast', add_args=[]): def get_podcast_group_link_target(group, view_name, add_args=[]): """ the link-target for a Podcast group, preferring slugs over Ids """ args = [group.slug] - view_name = '%s-slug-id' % view_name + view_name = "%s-slug-id" % view_name return reverse(view_name, args=args + add_args) -def get_episode_link_target(episode, podcast, view_name='episode', add_args=[]): +def get_episode_link_target(episode, podcast, view_name="episode", add_args=[]): """ Returns the link-target for an Episode, preferring slugs over Ids """ # prefer slugs if episode.slug: args = [podcast.slug, episode.slug] - view_name = '%s-slug' % view_name + view_name = "%s-slug" % view_name # fallback: UUIDs else: podcast = podcast or episode.podcast args = [podcast.id, episode.id] - view_name = '%s-id' % view_name + view_name = "%s-id" % view_name return strip_tags(reverse(view_name, args=args + add_args)) # doesn't include the '@' because it's not stored as part of a twitter handle -TWITTER_CHARS = string.ascii_letters + string.digits + '_' +TWITTER_CHARS = string.ascii_letters + string.digits + "_" def normalize_twitter(s): @@ -193,16 +193,16 @@ def normalize_twitter(s): CCLICENSE = re.compile( - r'https?://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?' + r"https?://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?" ) CCPUBLICDOMAIN = re.compile( - r'https?://(www\.)?creativecommons.org/licenses/publicdomain/?' + r"https?://(www\.)?creativecommons.org/licenses/publicdomain/?" ) -LicenseInfo = collections.namedtuple('LicenseInfo', 'name version url') +LicenseInfo = collections.namedtuple("LicenseInfo", "name version url") def license_info(license_url): - """ Extracts license information from the license URL + """Extracts license information from the license URL >>> i = license_info('http://creativecommons.org/licenses/by/3.0/') >>> i.name @@ -245,11 +245,11 @@ def license_info(license_url): m = CCLICENSE.match(license_url) if m: _, name, version = m.groups() - return LicenseInfo('CC %s' % name.upper(), version, license_url) + return LicenseInfo("CC %s" % name.upper(), version, license_url) m = CCPUBLICDOMAIN.match(license_url) if m: - return LicenseInfo('Public Domain', None, license_url) + return LicenseInfo("Public Domain", None, license_url) return LicenseInfo(None, None, license_url) @@ -257,7 +257,7 @@ def license_info(license_url): def check_restrictions(obj): """ checks for known restrictions of the object """ - restrictions = obj.restrictions.split(',') + restrictions = obj.restrictions.split(",") if "hide" in restrictions: raise Http404 @@ -268,7 +268,7 @@ def check_restrictions(obj): def hours_to_str(hours_total): - """ returns a human-readable string representation of some hours + """returns a human-readable string representation of some hours >>> hours_to_str(1) '1 hour' @@ -294,15 +294,15 @@ def hours_to_str(hours_total): if weeks: strs.append( - ngettext('%(weeks)d week', '%(weeks)d weeks', weeks) % {'weeks': weeks} + ngettext("%(weeks)d week", "%(weeks)d weeks", weeks) % {"weeks": weeks} ) if days: - strs.append(ngettext('%(days)d day', '%(days)d days', days) % {'days': days}) + strs.append(ngettext("%(days)d day", "%(days)d days", days) % {"days": days}) if hours: strs.append( - ngettext('%(hours)d hour', '%(hours)d hours', hours) % {'hours': hours} + ngettext("%(hours)d hour", "%(hours)d hours", hours) % {"hours": hours} ) - return ', '.join(strs) + return ", ".join(strs) diff --git a/mygpo/web/views.py b/mygpo/web/views.py index 07bdf21c3..e6630b858 100644 --- a/mygpo/web/views.py +++ b/mygpo/web/views.py @@ -43,7 +43,7 @@ def welcome(request): toplist = Podcast.objects.all().toplist(lang) - return render(request, 'home.html', {'url': current_site, 'toplist': toplist}) + return render(request, "home.html", {"url": current_site, "toplist": toplist}) @vary_on_cookie @@ -61,31 +61,31 @@ def dashboard(request, episode_count=10): checklist = [] if request.user.client_set.count(): - checklist.append('devices') + checklist.append("devices") if subscribed_podcasts: - checklist.append('subscriptions') + checklist.append("subscriptions") if FavoriteEpisode.objects.filter(user=request.user).exists(): - checklist.append('favorites') + checklist.append("favorites") - if not request.user.profile.get_token('subscriptions_token'): - checklist.append('share') + if not request.user.profile.get_token("subscriptions_token"): + checklist.append("share") - if not request.user.profile.get_token('favorite_feeds_token'): - checklist.append('share-favorites') + if not request.user.profile.get_token("favorite_feeds_token"): + checklist.append("share-favorites") - if not request.user.profile.get_token('userpage_token'): - checklist.append('userpage') + if not request.user.profile.get_token("userpage_token"): + checklist.append("userpage") if Tag.objects.filter(user=request.user).exists(): - checklist.append('tags') + checklist.append("tags") if PodcastList.objects.filter(user=request.user).exists(): - checklist.append('lists') + checklist.append("lists") if PublishedPodcast.objects.filter(publisher=request.user).exists(): - checklist.append('publish') + checklist.append("publish") tomorrow = datetime.today() + timedelta(days=1) @@ -102,22 +102,22 @@ def dashboard(request, episode_count=10): # we only show the "install reader" link in firefox, because we don't know # yet how/if this works in other browsers. # hints appreciated at https://bugs.gpodder.org/show_bug.cgi?id=58 - show_install_reader = 'firefox' in request.META.get('HTTP_USER_AGENT', '').lower() + show_install_reader = "firefox" in request.META.get("HTTP_USER_AGENT", "").lower() - random_podcast = Podcast.objects.all().random().prefetch_related('slugs').first() + random_podcast = Podcast.objects.all().random().prefetch_related("slugs").first() return render( request, - 'dashboard.html', + "dashboard.html", { - 'user': request.user, - 'subscribed_podcasts': subscribed_podcasts, - 'newest_episodes': list(newest_episodes), - 'random_podcast': random_podcast, - 'checklist': checklist, - 'site': site, - 'show_install_reader': show_install_reader, - 'podcast_ad': podcast_ad, + "user": request.user, + "subscribed_podcasts": subscribed_podcasts, + "newest_episodes": list(newest_episodes), + "random_podcast": random_podcast, + "checklist": checklist, + "site": site, + "show_install_reader": show_install_reader, + "podcast_ad": podcast_ad, }, ) @@ -130,11 +130,11 @@ def mytags(request): user = request.user - tags = Tag.objects.filter(source=Tag.USER, user=user).order_by('tag') + tags = Tag.objects.filter(source=Tag.USER, user=user).order_by("tag") for tag in tags: tags_tag[tag.tag].append(tag.content_object) - return render(request, 'mytags.html', {'tags_tag': dict(tags_tag.items())}) + return render(request, "mytags.html", {"tags_tag": dict(tags_tag.items())}) @never_cache @@ -142,13 +142,13 @@ def csrf_failure(request, reason=""): site = RequestSite(request) return render( request, - 'csrf.html', + "csrf.html", { - 'site': site, - 'method': request.method, - 'referer': request.META.get('HTTP_REFERER', _('another site')), - 'path': request.path, - 'get': request.GET, - 'post': request.POST, + "site": site, + "method": request.method, + "referer": request.META.get("HTTP_REFERER", _("another site")), + "path": request.path, + "get": request.GET, + "post": request.POST, }, ) diff --git a/requirements-dev.txt b/requirements-dev.txt index 400b1c53d..567d9ce9e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,8 +1,8 @@ -transifex-client==0.13.7 +transifex-client==0.14.2 sphinx pep8 flake8 -django-debug-toolbar==2.2 +django-debug-toolbar==3.2 django-extensions jupyter -black==19.10b0 +black==20.8b1 diff --git a/requirements-setup.txt b/requirements-setup.txt index 365ca2e64..aef18f827 100644 --- a/requirements-setup.txt +++ b/requirements-setup.txt @@ -1,3 +1,3 @@ envdir -gevent==1.4.0 -sentry-sdk==0.14.1 +gevent==20.9.0 +sentry-sdk==0.19.5 \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt index 822ed1be0..7610292a1 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,8 @@ -coverage==5.0.3 +coverage==5.3.1 django-coverage-plugin==1.8.0 +pytest>=4.6 pytest-django pytest-cov -responses==0.10.9 -black==19.10b0 -openapi-spec-validator \ No newline at end of file +responses==0.12.1 +black==20.8b1 +openapi-spec-validator diff --git a/requirements.txt b/requirements.txt index 603ef5679..97220c6c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,21 @@ -Django==3.0.2 -Babel==2.8.0 -Pillow==7.0.0 -celery[redis]==4.4.0 +Django<3.2 +celery<5.0,>=4.4 +Babel==2.9.0 +Pillow==8.0.1 +celery[redis]==4.4.7 dj-database-url==0.5.0 django-redis-sessions==0.6.1 python3-memcached==1.51 -feedparser==5.2.1 +feedparser==6.0.2 gunicorn==20.0.4 html2text==2020.1.16 -markdown2==2.3.8 +markdown2==2.3.10 oauth2client==4.1.3 psycopg2cffi==2.8.1 python-dateutil==2.8.1 -redis==3.3.11 -django-celery-results==1.2.0 -django-celery-beat==1.5.0 -requests==2.22.0 +redis==3.5.3 +django-celery-results==2.0.0 +django-celery-beat==2.1.0 +requests==2.25.1 whitenoise==5.0.1 -django-db-geventpool==3.1.0 +django-db-geventpool==4.0.0 diff --git a/tools/i18n/generate_commits.py b/tools/i18n/generate_commits.py index 98127819b..e2c4a6804 100755 --- a/tools/i18n/generate_commits.py +++ b/tools/i18n/generate_commits.py @@ -12,15 +12,25 @@ filenames = [] -pofiles = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..', '..', '..', 'mygpo', 'locale', '*', 'LC_MESSAGES', 'django.po') +pofiles = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "..", + "..", + "mygpo", + "locale", + "*", + "LC_MESSAGES", + "django.po", +) -process = subprocess.Popen(['git', 'status', '--porcelain'] + - glob.glob(pofiles), stdout=subprocess.PIPE) +process = subprocess.Popen( + ["git", "status", "--porcelain"] + glob.glob(pofiles), stdout=subprocess.PIPE +) stdout, stderr = process.communicate() for line in stdout.splitlines(): status, filename = line.strip().split() - if status == 'M': + if status == "M": filenames.append(filename) for filename in filenames: @@ -28,18 +38,18 @@ translators = [] language = None - filename = os.path.join('..', '..', filename) + filename = os.path.join("..", "..", filename) for line in open(filename).read().splitlines(): - if line.startswith('# Translators:'): + if line.startswith("# Translators:"): in_translators = True elif in_translators: - match = re.match(r'# ([^<]* <[^>]*>)', line) + match = re.match(r"# ([^<]* <[^>]*>)", line) if match: translators.append(match.group(1)) else: in_translators = False - match = re.search(r'Last-Translator: ([^<]* <[^>]*>)', line) + match = re.search(r"Last-Translator: ([^<]* <[^>]*>)", line) if match: translators.append(match.group(1)) @@ -51,8 +61,10 @@ if translators and language is not None: if len(translators) != 1: - print('# Warning: %d other translators' % (len(translators) - 1,)) - print('git commit --author="%s" --message="Updated %s translation" %s' % (translators[0], language, filename)) + print("# Warning: %d other translators" % (len(translators) - 1,)) + print( + 'git commit --author="%s" --message="Updated %s translation" %s' + % (translators[0], language, filename) + ) else: - print('# FIXME (could not parse):', '!'*10, filename, '!'*10) - + print("# FIXME (could not parse):", "!" * 10, filename, "!" * 10) diff --git a/tools/i18n/summary.py b/tools/i18n/summary.py index 0e75c731d..4aea8cfd4 100755 --- a/tools/i18n/summary.py +++ b/tools/i18n/summary.py @@ -14,6 +14,7 @@ width = 40 + class Language(object): def __init__(self, language, translated, fuzzy, untranslated): self.language = language @@ -22,40 +23,55 @@ def __init__(self, language, translated, fuzzy, untranslated): self.untranslated = int(untranslated) def get_translated_ratio(self): - return float(self.translated)/float(self.translated+self.fuzzy+self.untranslated) + return float(self.translated) / float( + self.translated + self.fuzzy + self.untranslated + ) def get_fuzzy_ratio(self): - return float(self.fuzzy)/float(self.translated+self.fuzzy+self.untranslated) + return float(self.fuzzy) / float( + self.translated + self.fuzzy + self.untranslated + ) def get_untranslated_ratio(self): - return float(self.untranslated)/float(self.translated+self.fuzzy+self.untranslated) + return float(self.untranslated) / float( + self.translated + self.fuzzy + self.untranslated + ) def __cmp__(self, other): return cmp(self.get_translated_ratio(), other.get_translated_ratio()) + languages = [] -COUNTS_RE = '((\d+) translated message[s]?)?(, (\d+) fuzzy translation[s]?)?(, (\d+) untranslated message[s]?)?\.' +COUNTS_RE = "((\d+) translated message[s]?)?(, (\d+) fuzzy translation[s]?)?(, (\d+) untranslated message[s]?)?\." -po_folder = os.path.join(os.path.dirname(__file__), '..', '..', 'mygpo', 'locale') -for filename in glob.glob(os.path.join(po_folder, '*', 'LC_MESSAGES', 'django.po')): - language = filename.split('/')[-3] - msgfmt = subprocess.Popen(['msgfmt', '--statistics', filename], - stderr=subprocess.PIPE) +po_folder = os.path.join(os.path.dirname(__file__), "..", "..", "mygpo", "locale") +for filename in glob.glob(os.path.join(po_folder, "*", "LC_MESSAGES", "django.po")): + language = filename.split("/")[-3] + msgfmt = subprocess.Popen( + ["msgfmt", "--statistics", filename], stderr=subprocess.PIPE + ) _, stderr = msgfmt.communicate() match = re.match(COUNTS_RE, stderr).groups() - languages.append(Language(language, match[1] or '0', match[3] or '0', match[5] or '0')) + languages.append( + Language(language, match[1] or "0", match[3] or "0", match[5] or "0") + ) -print('') +print("") for language in sorted(languages): - tc = '#'*(int(math.floor(width*language.get_translated_ratio()))) - fc = '~'*(int(math.floor(width*language.get_fuzzy_ratio()))) - uc = ' '*(width-len(tc)-len(fc)) + tc = "#" * (int(math.floor(width * language.get_translated_ratio()))) + fc = "~" * (int(math.floor(width * language.get_fuzzy_ratio()))) + uc = " " * (width - len(tc) - len(fc)) - print(' %5s [%s%s%s] -- %3.0f %% translated' % (language.language, tc, fc, uc, language.get_translated_ratio()*100)) + print( + " %5s [%s%s%s] -- %3.0f %% translated" + % (language.language, tc, fc, uc, language.get_translated_ratio() * 100) + ) -print(""" +print( + """ Total translations: %s -""" % (len(languages))) - +""" + % (len(languages)) +)