pep257ifys core and api (#2245)
parent
b5c8f87abe
commit
d4762d9d0f
|
@ -34,6 +34,10 @@ mccabe:
|
||||||
|
|
||||||
pep257:
|
pep257:
|
||||||
run: false
|
run: false
|
||||||
|
disable:
|
||||||
|
- D105
|
||||||
|
- D211
|
||||||
|
- D104
|
||||||
|
|
||||||
pyflakes:
|
pyflakes:
|
||||||
disable:
|
disable:
|
||||||
|
|
|
@ -19,13 +19,16 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SearchMixin(object):
|
class SearchMixin(object):
|
||||||
'''
|
|
||||||
|
"""
|
||||||
Adds a search api to any ModelResource provided the model is indexed.
|
Adds a search api to any ModelResource provided the model is indexed.
|
||||||
|
|
||||||
The search can be configured using the Meta class in each ModelResource.
|
The search can be configured using the Meta class in each ModelResource.
|
||||||
The search is limited to the model defined by the meta queryset. If the
|
The search is limited to the model defined by the meta queryset. If the
|
||||||
search is invalid, a 400 Bad Request will be raised.
|
search is invalid, a 400 Bad Request will be raised.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
# Return facet counts for each facetname
|
# Return facet counts for each facetname
|
||||||
search_facets = ['facetname1', 'facetname1']
|
search_facets = ['facetname1', 'facetname1']
|
||||||
|
@ -35,7 +38,8 @@ class SearchMixin(object):
|
||||||
|
|
||||||
# Highlight search terms in the text
|
# Highlight search terms in the text
|
||||||
search_highlight = True
|
search_highlight = True
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def get_search(self, request, **kwargs):
|
def get_search(self, request, **kwargs):
|
||||||
self.method_check(request, allowed=['get'])
|
self.method_check(request, allowed=['get'])
|
||||||
self.is_authenticated(request)
|
self.is_authenticated(request)
|
||||||
|
@ -49,11 +53,12 @@ class SearchMixin(object):
|
||||||
return self.create_response(request, object_list)
|
return self.create_response(request, object_list)
|
||||||
|
|
||||||
def _url_template(self, query, selected_facets):
|
def _url_template(self, query, selected_facets):
|
||||||
'''
|
"""
|
||||||
Construct a url template to assist with navigating the resources.
|
Construct a url template to assist with navigating the resources.
|
||||||
|
|
||||||
This looks a bit nasty but urllib.urlencode resulted in even
|
This looks a bit nasty but urllib.urlencode resulted in even
|
||||||
nastier output...
|
nastier output...
|
||||||
'''
|
"""
|
||||||
query_params = []
|
query_params = []
|
||||||
for facet in selected_facets:
|
for facet in selected_facets:
|
||||||
query_params.append(('selected_facets', facet))
|
query_params.append(('selected_facets', facet))
|
||||||
|
@ -67,12 +72,13 @@ class SearchMixin(object):
|
||||||
|
|
||||||
def _search(self, request, model, facets=None, page_size=20,
|
def _search(self, request, model, facets=None, page_size=20,
|
||||||
highlight=True):
|
highlight=True):
|
||||||
'''
|
"""
|
||||||
`facets`
|
`facets`
|
||||||
|
|
||||||
A list of facets to include with the results
|
A list of facets to include with the results
|
||||||
`models`
|
`models`
|
||||||
Limit the search to one or more models
|
Limit the search to one or more models
|
||||||
'''
|
"""
|
||||||
form = FacetedSearchForm(request.GET, facets=facets or [],
|
form = FacetedSearchForm(request.GET, facets=facets or [],
|
||||||
models=(model,), load_all=True)
|
models=(model,), load_all=True)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
|
|
|
@ -42,17 +42,19 @@ class UserProfileForm(forms.ModelForm):
|
||||||
|
|
||||||
class FacetField(forms.MultipleChoiceField):
|
class FacetField(forms.MultipleChoiceField):
|
||||||
|
|
||||||
'''
|
"""
|
||||||
For filtering searches on a facet, with validation for the format
|
For filtering searches on a facet.
|
||||||
of facet values.
|
|
||||||
'''
|
Has validation for the format of facet values.
|
||||||
|
"""
|
||||||
|
|
||||||
def valid_value(self, value):
|
def valid_value(self, value):
|
||||||
'''
|
"""
|
||||||
Although this is a choice field, no choices need to be supplied.
|
Although this is a choice field, no choices need to be supplied.
|
||||||
|
|
||||||
Instead, we just validate that the value is in the correct format
|
Instead, we just validate that the value is in the correct format
|
||||||
for facet filtering (facet_name:value)
|
for facet filtering (facet_name:value)
|
||||||
'''
|
"""
|
||||||
if ":" not in value:
|
if ":" not in value:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -60,14 +62,14 @@ class FacetField(forms.MultipleChoiceField):
|
||||||
|
|
||||||
class FacetedSearchForm(SearchForm):
|
class FacetedSearchForm(SearchForm):
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Supports fetching faceted results with a corresponding query.
|
Supports fetching faceted results with a corresponding query.
|
||||||
|
|
||||||
`facets`
|
`facets`
|
||||||
A list of facet names for which to get facet counts
|
A list of facet names for which to get facet counts
|
||||||
`models`
|
`models`
|
||||||
Limit the search to one or more models
|
Limit the search to one or more models
|
||||||
'''
|
"""
|
||||||
|
|
||||||
selected_facets = FacetField(required=False)
|
selected_facets = FacetField(required=False)
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,11 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""Custom management command to rebuild documentation for all projects on
|
|
||||||
the site. Invoked via ``./manage.py update_repos``.
|
"""
|
||||||
|
Custom management command to rebuild documentation for all projects.
|
||||||
|
|
||||||
|
Invoked via ``./manage.py update_repos``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
@ -27,9 +27,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
'''
|
"""Find stale builds and remove build paths"""
|
||||||
Find stale builds and remove build paths
|
|
||||||
'''
|
|
||||||
max_date = datetime.now() - timedelta(days=options['days'])
|
max_date = datetime.now() - timedelta(days=options['days'])
|
||||||
queryset = (Build.objects
|
queryset = (Build.objects
|
||||||
.values('project', 'version')
|
.values('project', 'version')
|
||||||
|
|
|
@ -22,9 +22,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
'''
|
"""Build/index all versions or a single project's version"""
|
||||||
Build/index all versions or a single project's version
|
|
||||||
'''
|
|
||||||
project = options['project']
|
project = options['project']
|
||||||
|
|
||||||
queryset = Version.objects.public()
|
queryset = Version.objects.public()
|
||||||
|
|
|
@ -9,8 +9,11 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""Custom management command to rebuild documentation for all projects on
|
|
||||||
the site. Invoked via ``./manage.py update_repos``.
|
"""
|
||||||
|
Custom management command to rebuild documentation for all projects.
|
||||||
|
|
||||||
|
Invoked via ``./manage.py update_repos``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
|
|
|
@ -9,6 +9,7 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Build documentation using the API and not hitting a database.
|
Build documentation using the API and not hitting a database.
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,10 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
"""Custom management command to rebuild documentation for all projects on
|
"""
|
||||||
the site. Invoked via ``./manage.py update_repos``.
|
Custom management command to rebuild documentation for all projects.
|
||||||
|
|
||||||
|
Invoked via ``./manage.py update_repos``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
|
|
|
@ -5,8 +5,11 @@ from readthedocs.projects.tasks import update_docs
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""Custom management command to rebuild documentation for all projects on
|
|
||||||
the site. Invoked via ``./manage.py update_repos``.
|
"""
|
||||||
|
Custom management command to rebuild documentation for all projects.
|
||||||
|
|
||||||
|
Invoked via ``./manage.py update_repos``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
@ -178,6 +178,7 @@ class ProxyMiddleware(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
|
Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
|
||||||
|
|
||||||
latter is set. This is useful if you're sitting behind a reverse proxy that
|
latter is set. This is useful if you're sitting behind a reverse proxy that
|
||||||
causes each request's REMOTE_ADDR to be set to 127.0.0.1.
|
causes each request's REMOTE_ADDR to be set to 127.0.0.1.
|
||||||
Note that this does NOT validate HTTP_X_FORWARDED_FOR. If you're not behind
|
Note that this does NOT validate HTTP_X_FORWARDED_FOR. If you're not behind
|
||||||
|
@ -207,6 +208,7 @@ class FooterNoSessionMiddleware(SessionMiddleware):
|
||||||
|
|
||||||
This will reduce the size of our session table drastically.
|
This will reduce the size of our session table drastically.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
IGNORE_URLS = ['/api/v2/footer_html', '/sustainability/view', '/sustainability/click']
|
IGNORE_URLS = ['/api/v2/footer_html', '/sustainability/view', '/sustainability/click']
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
"""
|
"""Common mixin classes for views"""
|
||||||
Common mixin classes for views
|
|
||||||
"""
|
|
||||||
|
|
||||||
from vanilla import ListView
|
from vanilla import ListView
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
|
@ -14,8 +14,8 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UserProfile (models.Model):
|
class UserProfile (models.Model):
|
||||||
|
|
||||||
"""Additional information about a User.
|
"""Additional information about a User."""
|
||||||
"""
|
|
||||||
user = AutoOneToOneField('auth.User', verbose_name=_('User'),
|
user = AutoOneToOneField('auth.User', verbose_name=_('User'),
|
||||||
related_name='profile')
|
related_name='profile')
|
||||||
whitelisted = models.BooleanField(_('Whitelisted'), default=False)
|
whitelisted = models.BooleanField(_('Whitelisted'), default=False)
|
||||||
|
|
|
@ -69,6 +69,7 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Symlink(object):
|
class Symlink(object):
|
||||||
|
|
||||||
"""Base class for symlinking of projects."""
|
"""Base class for symlinking of projects."""
|
||||||
|
|
||||||
def __init__(self, project):
|
def __init__(self, project):
|
||||||
|
|
|
@ -14,10 +14,11 @@ register = template.Library()
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def gravatar(email, size=48):
|
def gravatar(email, size=48):
|
||||||
"""hacked from djangosnippets.org, but basically given an email address
|
"""
|
||||||
|
hacked from djangosnippets.org, but basically given an email address
|
||||||
|
|
||||||
render an img tag with the hashed up bits needed for leetness
|
render an img tag with the hashed up bits needed for leetness
|
||||||
omgwtfstillreading
|
omgwtfstillreading
|
||||||
|
|
||||||
"""
|
"""
|
||||||
url = "http://www.gravatar.com/avatar.php?%s" % urllib.urlencode({
|
url = "http://www.gravatar.com/avatar.php?%s" % urllib.urlencode({
|
||||||
'gravatar_id': hashlib.md5(email).hexdigest(),
|
'gravatar_id': hashlib.md5(email).hexdigest(),
|
||||||
|
|
|
@ -16,9 +16,7 @@ SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())
|
||||||
|
|
||||||
|
|
||||||
def run_on_app_servers(command):
|
def run_on_app_servers(command):
|
||||||
"""
|
"""A helper to copy a single file across app servers"""
|
||||||
A helper to copy a single file across app servers
|
|
||||||
"""
|
|
||||||
log.info("Running %s on app servers" % command)
|
log.info("Running %s on app servers" % command)
|
||||||
ret_val = 0
|
ret_val = 0
|
||||||
if getattr(settings, "MULTIPLE_APP_SERVERS", None):
|
if getattr(settings, "MULTIPLE_APP_SERVERS", None):
|
||||||
|
|
|
@ -14,25 +14,22 @@ STATUS_UPDATES_ENABLED = not getattr(settings, 'CELERY_ALWAYS_EAGER', False)
|
||||||
|
|
||||||
|
|
||||||
class PublicTask(Task):
|
class PublicTask(Task):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
See oauth.tasks for usage example.
|
See oauth.tasks for usage example.
|
||||||
|
|
||||||
Subclasses need to define a ``run_public`` method.
|
Subclasses need to define a ``run_public`` method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
public_name = 'unknown'
|
public_name = 'unknown'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_permission(cls, request, state, context):
|
def check_permission(cls, request, state, context):
|
||||||
"""
|
"""Override this method to define who can monitor this task."""
|
||||||
Override this method to define who can monitor this task.
|
|
||||||
"""
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_task_data(self):
|
def get_task_data(self):
|
||||||
"""
|
"""Return tuple with state to be set next and results task."""
|
||||||
Return a tuple with the state that should be set next and the results
|
|
||||||
task.
|
|
||||||
"""
|
|
||||||
state = 'STARTED'
|
state = 'STARTED'
|
||||||
info = {
|
info = {
|
||||||
'task_name': self.name,
|
'task_name': self.name,
|
||||||
|
@ -49,6 +46,7 @@ class PublicTask(Task):
|
||||||
def set_permission_context(self, context):
|
def set_permission_context(self, context):
|
||||||
"""
|
"""
|
||||||
Set data that can be used by ``check_permission`` to authorize a
|
Set data that can be used by ``check_permission`` to authorize a
|
||||||
|
|
||||||
request for the this task. By default it will be the ``kwargs`` passed
|
request for the this task. By default it will be the ``kwargs`` passed
|
||||||
into the task.
|
into the task.
|
||||||
"""
|
"""
|
||||||
|
@ -58,6 +56,7 @@ class PublicTask(Task):
|
||||||
def set_public_data(self, data):
|
def set_public_data(self, data):
|
||||||
"""
|
"""
|
||||||
Set data that can be displayed in the frontend to authorized users.
|
Set data that can be displayed in the frontend to authorized users.
|
||||||
|
|
||||||
This might include progress data about the task.
|
This might include progress data about the task.
|
||||||
"""
|
"""
|
||||||
self.request.update(public_data=data)
|
self.request.update(public_data=data)
|
||||||
|
@ -83,6 +82,7 @@ class PublicTask(Task):
|
||||||
def permission_check(check):
|
def permission_check(check):
|
||||||
"""
|
"""
|
||||||
Class decorator for subclasses of PublicTask to sprinkle in re-usable
|
Class decorator for subclasses of PublicTask to sprinkle in re-usable
|
||||||
|
|
||||||
permission checks::
|
permission checks::
|
||||||
|
|
||||||
@permission_check(user_id_matches)
|
@permission_check(user_id_matches)
|
||||||
|
@ -90,7 +90,6 @@ def permission_check(check):
|
||||||
def run_public(self, user_id):
|
def run_public(self, user_id):
|
||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(cls):
|
def decorator(cls):
|
||||||
cls.check_permission = staticmethod(check)
|
cls.check_permission = staticmethod(check)
|
||||||
return cls
|
return cls
|
||||||
|
|
|
@ -14,9 +14,9 @@ class TaskNotFound(Exception):
|
||||||
def get_task_data(task_id):
|
def get_task_data(task_id):
|
||||||
"""
|
"""
|
||||||
Will raise `TaskNotFound` if the task is in state ``PENDING`` or the task
|
Will raise `TaskNotFound` if the task is in state ``PENDING`` or the task
|
||||||
|
|
||||||
meta data has no ``'task_name'`` key set.
|
meta data has no ``'task_name'`` key set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = AsyncResult(task_id)
|
result = AsyncResult(task_id)
|
||||||
state, info = result.state, result.info
|
state, info = result.state, result.info
|
||||||
if state == 'PENDING':
|
if state == 'PENDING':
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""
|
"""
|
||||||
Core views, including the main homepage,
|
Core views, including the main homepage,
|
||||||
|
|
||||||
documentation and header rendering, and server errors.
|
documentation and header rendering, and server errors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ class HomepageView(DonateProgressMixin, TemplateView):
|
||||||
template_name = 'homepage.html'
|
template_name = 'homepage.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
'''Add latest builds and featured projects'''
|
"""Add latest builds and featured projects"""
|
||||||
context = super(HomepageView, self).get_context_data(**kwargs)
|
context = super(HomepageView, self).get_context_data(**kwargs)
|
||||||
latest = []
|
latest = []
|
||||||
latest_builds = (
|
latest_builds = (
|
||||||
|
@ -107,9 +108,7 @@ def divide_by_zero(request):
|
||||||
|
|
||||||
|
|
||||||
def server_error(request, template_name='500.html'):
|
def server_error(request, template_name='500.html'):
|
||||||
"""
|
"""A simple 500 handler so we get media"""
|
||||||
A simple 500 handler so we get media
|
|
||||||
"""
|
|
||||||
r = render_to_response(template_name,
|
r = render_to_response(template_name,
|
||||||
context_instance=RequestContext(request))
|
context_instance=RequestContext(request))
|
||||||
r.status_code = 500
|
r.status_code = 500
|
||||||
|
@ -117,9 +116,7 @@ def server_error(request, template_name='500.html'):
|
||||||
|
|
||||||
|
|
||||||
def server_error_404(request, template_name='404.html'):
|
def server_error_404(request, template_name='404.html'):
|
||||||
"""
|
"""A simple 404 handler so we get media"""
|
||||||
A simple 404 handler so we get media
|
|
||||||
"""
|
|
||||||
response = get_redirect_response(request, path=request.get_full_path())
|
response = get_redirect_response(request, path=request.get_full_path())
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
|
@ -132,6 +129,7 @@ def server_error_404(request, template_name='404.html'):
|
||||||
class SendEmailView(FormView):
|
class SendEmailView(FormView):
|
||||||
|
|
||||||
"""Form view for sending emails to users from admin pages
|
"""Form view for sending emails to users from admin pages
|
||||||
|
|
||||||
Accepts the following additional parameters:
|
Accepts the following additional parameters:
|
||||||
queryset
|
queryset
|
||||||
The queryset to use to determine the users to send emails to
|
The queryset to use to determine the users to send emails to
|
||||||
|
@ -142,6 +140,7 @@ class SendEmailView(FormView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Override form kwargs based on input fields
|
"""Override form kwargs based on input fields
|
||||||
|
|
||||||
The admin posts to this view initially, so detect the send button on
|
The admin posts to this view initially, so detect the send button on
|
||||||
form post variables. Drop additional fields if we see the send button.
|
form post variables. Drop additional fields if we see the send button.
|
||||||
"""
|
"""
|
||||||
|
@ -189,6 +188,7 @@ class SendEmailView(FormView):
|
||||||
def message_user(self, message, level=messages.INFO, extra_tags='',
|
def message_user(self, message, level=messages.INFO, extra_tags='',
|
||||||
fail_silently=False):
|
fail_silently=False):
|
||||||
"""Implementation of :py:meth:`django.contrib.admin.options.ModelAdmin.message_user`
|
"""Implementation of :py:meth:`django.contrib.admin.options.ModelAdmin.message_user`
|
||||||
|
|
||||||
Send message through messages framework
|
Send message through messages framework
|
||||||
"""
|
"""
|
||||||
# TODO generalize this or check if implementation in ModelAdmin is
|
# TODO generalize this or check if implementation in ModelAdmin is
|
||||||
|
|
|
@ -136,9 +136,7 @@ def _build_url(url, projects, branches):
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def github_build(request):
|
def github_build(request):
|
||||||
"""
|
"""A post-commit hook for github."""
|
||||||
A post-commit hook for github.
|
|
||||||
"""
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
try:
|
try:
|
||||||
# GitHub RTD integration
|
# GitHub RTD integration
|
||||||
|
@ -176,9 +174,7 @@ def github_build(request):
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def gitlab_build(request):
|
def gitlab_build(request):
|
||||||
"""
|
"""A post-commit hook for GitLab."""
|
||||||
A post-commit hook for GitLab.
|
|
||||||
"""
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
try:
|
try:
|
||||||
# GitLab RTD integration
|
# GitLab RTD integration
|
||||||
|
|
|
@ -134,9 +134,7 @@ def _serve_file(request, filename, basepath):
|
||||||
@map_subproject_slug
|
@map_subproject_slug
|
||||||
def serve_docs(request, project, subproject,
|
def serve_docs(request, project, subproject,
|
||||||
lang_slug=None, version_slug=None, filename=''):
|
lang_slug=None, version_slug=None, filename=''):
|
||||||
"""
|
"""Exists to map existing proj, lang, version, filename views to the file format."""
|
||||||
This exists mainly to map existing proj, lang, version, filename views to the file format.
|
|
||||||
"""
|
|
||||||
if not version_slug:
|
if not version_slug:
|
||||||
version_slug = project.get_default_version()
|
version_slug = project.get_default_version()
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue