diff --git a/media/css/core.css b/media/css/core.css index b0f39b1dd..e35e66e73 100644 --- a/media/css/core.css +++ b/media/css/core.css @@ -76,7 +76,7 @@ label { display: block; margin-bottom: 4px; font-weight: bold; color: #444; } #content { padding-top: 50px; } #content ul { margin-bottom: 20px; } -#content #project_description { background-color: #eee; } +#content #project_description { margin-bottom: 20px; } /* Commenting this for now. Want to figure out how to make it look sane with small amounts of text. */ /* #content p { background-color: #eee; } */ @@ -141,6 +141,10 @@ i { display: inline-block; padding:0; margin:0; padding-right:6px; position: rel /* search */ +.search { border-bottom: solid 1px #bfbfbf; } +.search input[type=text] { float: left; margin-right: 10px; padding: 8px 10px; } +.search input[type=submit] { margin-top: 0; } + .filter { margin-bottom: 1em; } .filter dd { display: inline-block; margin-right: 0.75em; } .filter dd small { opacity: 0.7; } @@ -325,6 +329,34 @@ p.build-missing { font-size: .8em; color: #9d9a55; margin: 0 0 3px; } .clearfix:after, .wrapper:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; overflow: hidden; } .clearfix, .wrapper { display: block; } +/* project detail */ +.project_detail .module { + float: left; + width: 500px; +} + +.project_detail .module .help_text { + font-size: 14px; +} + +.project_detail .help_text em { + color: #666; +} + +.project_detail .build_a_version { + margin-top: 40px; +} + +.project_detail .project_details { + float: right; + width: 250px; + margin-top: 34px; +} + +.project_detail .project_details h3 { + margin-bottom: 0; + font-size: 16px; +} .when-editing { opacity: 0; -webkit-transition:opacity 0.2s ease-in-out; -moz-transition:opacity 0.2s ease-in-out; -ms-transition:opacity 0.2s ease-in-out; -o-transition:opacity 0.2s ease-in-out; transition:opacity 0.2s ease-in-out; pointer-events:none; -webkit-user-select:none; } .editing .when-editing { opacity: 1; } @@ -365,4 +397,3 @@ select.dropdown { display: none; } .dropdown > ul.js-open { display:block; } .dropdown > ul:before { content:' '; visibility: visible; border:8px solid transparent; border-bottom-color: #465158; position:absolute; top:-16px; left:104px; } - diff --git a/readthedocs/builds/filters.py b/readthedocs/builds/filters.py index 384d879ad..353cd4af3 100644 --- a/readthedocs/builds/filters.py +++ b/readthedocs/builds/filters.py @@ -12,10 +12,17 @@ ANY_REPO = ( BUILD_TYPES = ANY_REPO + constants.BUILD_TYPES +class VersionSlugFilter(django_filters.FilterSet): + slug = django_filters.CharFilter(label=_("Name"), name='slug', lookup_type='icontains') + tag = django_filters.CharFilter(label=_("Tag"), name='tags', lookup_type='name__icontains') + + class Meta: + model = Version + fields = ['slug', 'tag'] class VersionFilter(django_filters.FilterSet): project = django_filters.CharFilter(name='project__name', lookup_type="icontains") - slug= django_filters.CharFilter(label=_("Slug"), name='slug', lookup_type='icontains') + slug = django_filters.CharFilter(label=_("Name"), name='slug', lookup_type='icontains') class Meta: model = Version diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index 9d5aec940..62d3eac8c 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -1,9 +1,8 @@ from django import forms -from builds.models import VersionAlias +from builds.models import VersionAlias, Version from projects.models import Project - class AliasForm(forms.ModelForm): class Meta: model = VersionAlias @@ -12,3 +11,10 @@ class AliasForm(forms.ModelForm): super(AliasForm, self).__init__(instance=instance, *args, **kwargs) if instance: self.fields['project'].queryset = Project.objects.filter(pk=instance.project.pk) + + +class VersionForm(forms.ModelForm): + + class Meta: + model = Version + fields = ['active', 'privacy_level', 'tags'] diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index c86c84b23..75edb72a3 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -2,6 +2,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _, ugettext from guardian.shortcuts import assign, get_objects_for_user +from taggit.managers import TaggableManager from projects.models import Project from projects import constants @@ -80,6 +81,7 @@ class Version(models.Model): choices=constants.PRIVACY_CHOICES, default='public', help_text=_("Level of privacy for this Version.")) + tags = TaggableManager(blank=True) objects = VersionManager() class Meta: diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index eb8abe230..d22f6f447 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -37,6 +37,10 @@ urlpatterns = patterns('projects.views.private', 'project_edit', name='projects_edit' ), + url(r'^(?P[-\w]+)/version/(?P[-\w.]+)/$', + 'project_version_detail', + name='project_version_detail' + ), url(r'^(?P[-\w]+)/versions/$', 'project_versions', name='projects_versions' diff --git a/readthedocs/projects/urls/public.py b/readthedocs/projects/urls/public.py index d23bf908b..f0f208570 100644 --- a/readthedocs/projects/urls/public.py +++ b/readthedocs/projects/urls/public.py @@ -17,6 +17,14 @@ urlpatterns = patterns('projects.views.public', 'search_autocomplete', name='search_autocomplete', ), + url(r'^autocomplete/version/(?P[-\w]+)/$', + 'version_autocomplete', + name='version_autocomplete', + ), + url(r'^autocomplete/filter/version/(?P[-\w]+)/$', + 'version_filter_autocomplete', + name='version_filter_autocomplete', + ), url(r'^tags/(?P[-\w]+)/$', 'project_index', name='projects_tag_detail', diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index a1e0b1e05..9c733b43b 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -13,7 +13,7 @@ from django.views.generic.list_detail import object_list from guardian.shortcuts import assign -from builds.forms import AliasForm +from builds.forms import AliasForm, VersionForm from builds.filters import VersionFilter from builds.models import Version from projects.forms import (ImportProjectForm, build_versions_form, @@ -103,6 +103,24 @@ def project_versions(request, project_slug): context_instance=RequestContext(request) ) +@login_required +def project_version_detail(request, project_slug, version_slug): + project = get_object_or_404(request.user.projects.live(), slug=project_slug) + version = get_object_or_404(project.versions.all(), slug=version_slug) + + form = VersionForm(request.POST or None, instance=version) + + if request.method == 'POST' and form.is_valid(): + form.save() + url = reverse('projects_versions', args=[project.slug]) + return HttpResponseRedirect(url) + + return render_to_response( + 'projects/project_version_detail.html', + {'form': form, 'project': project, 'version': version}, + context_instance=RequestContext(request) + ) + @login_required def project_delete(request, project_slug): """ diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py index b9217d30c..71f189e4a 100644 --- a/readthedocs/projects/views/public.py +++ b/readthedocs/projects/views/public.py @@ -13,6 +13,8 @@ from guardian.decorators import permission_required_or_403 from guardian.shortcuts import get_objects_for_user from taggit.models import Tag +from builds.filters import VersionSlugFilter +from builds.models import Version from core.views import serve_docs from projects.models import Project from projects.utils import highest_version @@ -52,11 +54,13 @@ def project_detail(request, project_slug): queryset = Project.objects.protected(request.user) project = get_object_or_404(queryset, slug=project_slug) versions = project.versions.public(request.user, project) + filter = VersionSlugFilter(request.GET, queryset=versions) return render_to_response( 'projects/project_detail.html', { 'project': project, 'versions': versions, + 'filter': filter, }, context_instance=RequestContext(request), ) @@ -132,10 +136,51 @@ def search_autocomplete(request): term = request.GET['term'] else: raise Http404 - queryset = Project.objects.live(name__icontains=term)[:20] + queryset = Project.objects.public(request.user).filter(name__icontains=term)[:20] project_names = queryset.values_list('name', flat=True) json_response = json.dumps(list(project_names)) return HttpResponse(json_response, mimetype='text/javascript') +def version_autocomplete(request, project_slug): + """ + return a json list of version names + """ + queryset = Project.objects.protected(request.user) + project = get_object_or_404(queryset, slug=project_slug) + versions = Version.objects.public(request.user) + if 'term' in request.GET: + term = request.GET['term'] + else: + raise Http404 + version_queryset = versions.filter(slug__icontains=term)[:20] + + names = version_queryset.values_list('slug', flat=True) + json_response = simplejson.dumps(list(names)) + + return HttpResponse(json_response, mimetype='text/javascript') + +def version_filter_autocomplete(request, project_slug): + queryset = Project.objects.protected(request.user) + project = get_object_or_404(queryset, slug=project_slug) + versions = Version.objects.public(request.user) + filter = VersionSlugFilter(request.GET, queryset=versions) + format = request.GET.get('format', 'json') + + if format == 'json': + names = filter.qs.values_list('slug', flat=True) + json_response = simplejson.dumps(list(names)) + return HttpResponse(json_response, mimetype='text/javascript') + elif format == 'html': + return render_to_response( + 'core/version_list.html', + { + 'project': project, + 'versions': versions, + 'filter': filter, + }, + context_instance=RequestContext(request), + ) + else: + raise HttpResponse(status=400) diff --git a/readthedocs/templates/core/project_details.html b/readthedocs/templates/core/project_details.html index 7a52f9b63..85f0965bf 100644 --- a/readthedocs/templates/core/project_details.html +++ b/readthedocs/templates/core/project_details.html @@ -1,5 +1,4 @@ {% load i18n %} - {% load markup %} {% if project.description %} @@ -11,133 +10,146 @@ {% endif %} - -

{% trans "Search this project" %}

- -
-
- - - -
+
- - -

{% trans "Project Privacy Level" %}

-
-

- {{ project.get_privacy_level_display }} -

-
- + {% if versions %} -

{% trans "Versions" %}

+
+
+

{% trans "Versions" %}

+
-

- {% for version in versions %} -

+
+ + {% comment %} +
+
+ {{ filter.form }} + +
+
+ {% endcomment %} + +
+ +
+
-
-
- {% endfor %} -

+
+

Build a version

+
+
+ + +
+
+
+
{# END .module #} +{% endif %} -

Build a version

+
+

{% trans "Project Privacy Level" %}

+

-

-
- - -
-
+ {{ project.get_privacy_level_display }}

+
-
-{% endif %} - - -

{% trans "Short URLs" %}

-

- {{ project.slug }}.{{ PRODUCTION_DOMAIN }}
- {% if PRODUCTION_DOMAIN == "readthedocs.org" %} - {{ project.slug }}.rtfd.org - {% endif %} -

- -{% if project.django_packages_url %} -

{% trans "Open Comparison" %}

-

{% blocktrans with project.django_packages_url as url %}This project has more information available about it on Open Comparison!{% endblocktrans %}

-{% endif %} - -{% if project.crate_url %} -

{% trans "Crate" %}

-

{% blocktrans with project.crate_url as url %}This project has more information available about it on Crate!{% endblocktrans %}

-{% endif %} - -{% if project.subprojects.exists %} -

Sub Projects

-
    - {% for rel in project.subprojects.all %} -
  • {{ rel.child }}
  • - {% endfor %} -
-{% endif %} - - -

{% trans "Last Built" %}

-

{{ project.modified_date|timesince }} ago

- - -{% if project.repo %} -

{% trans "Repository" %}

-

{{ project.repo }}

-{% endif %} - -{% if project.project_url %} -

Home Page

-

- {{ project.project_url }} -

-{% endif %} - -{% if project.tags.count %} -

{% trans "Tags" %}

+

{% trans "Short URLs" %}

- {% for tag in project.tags.all %} - {{ tag.name }}{% if forloop.last %}{% else %}, {% endif %} - {% empty %} - {% trans "No tags" %} - {% endfor %} + {{ project.slug }}.{{ PRODUCTION_DOMAIN }}
+ {% if PRODUCTION_DOMAIN == "readthedocs.org" %} + {{ project.slug }}.rtfd.org + {% endif %}

-{% endif %} + {% if project.django_packages_url %} +

{% trans "Open Comparison" %}

+

{% blocktrans with project.django_packages_url as url %}This project has more information available about it on Open Comparison!{% endblocktrans %}

+ {% endif %} -{% if project.get_latest_revisions.count %} -

{% trans "Latest Revisions" %}

-
    - {% for revision in project.get_latest_revisions|slice:":5" %} -
  • {% blocktrans with revision.created_date|timesince as timesince %}{{ revision }} {{ timesince }} ago{% endblocktrans %}
  • - {% empty %} -
  • {% trans "No revisions" %}
  • - {% endfor %} -
-{% endif %} + {% if project.crate_url %} +

{% trans "Crate" %}

+

{% blocktrans with project.crate_url as url %}This project has more information available about it on Crate!{% endblocktrans %}

+ {% endif %} + + {% if project.subprojects.exists %} +

Sub Projects

+
    + {% for rel in project.subprojects.all %} +
  • {{ rel.child }}
  • + {% endfor %} +
+ {% endif %} + +

{% trans "Last Built" %}

+

{{ project.modified_date|timesince }} ago

+ + {% if project.repo %} +

{% trans "Repository" %}

+

{{ project.repo }}

+ {% endif %} + + {% if project.project_url %} +

Home Page

+

+ {{ project.project_url }} +

+ {% endif %} + + {% if project.tags.count %} +

{% trans "Tags" %}

+

+ {% for tag in project.tags.all %} + {{ tag.name }}{% if forloop.last %}{% else %}, {% endif %} + {% empty %} + {% trans "No tags" %} + {% endfor %} +

+ {% endif %} + + {% if project.get_latest_revisions.count %} +

{% trans "Latest Revisions" %}

+
    + {% for revision in project.get_latest_revisions|slice:":5" %} +
  • {% blocktrans with revision.created_date|timesince as timesince %}{{ revision }} {{ timesince }} ago{% endblocktrans %}
  • + {% empty %} +
  • {% trans "No revisions" %}
  • + {% endfor %} +
+ {% endif %}

{% trans "Latest version" %}

{{ project.get_default_branch }}

@@ -151,27 +163,28 @@

http://{{ PRODUCTION_DOMAIN }}{% url generic_build project.pk %}

{% endif %} -{% if pageview_list %} - -
-
+ {% if pageview_list %} + +
+
-
-

{% trans "Most viewed pages for this project" %}

-
- -
-
-
    - {% include "core/pageview_list_detailed.html" %} -
+
+

{% trans "Most viewed pages for this project" %}

-
+
+
+
    + {% include "core/pageview_list_detailed.html" %} +
+
+
+ +
-
- -{% endif %} + + {% endif %} +
{# END .project_details #} {% if request.user not in project.users.all %} {% include "projects/includes/flagging.html" %} diff --git a/readthedocs/templates/core/version_list.html b/readthedocs/templates/core/version_list.html new file mode 100644 index 000000000..074f0f08c --- /dev/null +++ b/readthedocs/templates/core/version_list.html @@ -0,0 +1,18 @@ +
+
    + {% for version in filter %} +
  • + {# Link to the docs #} + {{ version.slug }} + {% if request.user in project.users.all %} + {{ version.get_privacy_level_display }} + {% endif %} + {% if request.user in project.users.all %} + + {% endif %} +
  • + {% endfor %} +
+
diff --git a/readthedocs/templates/projects/project_version_detail.html b/readthedocs/templates/projects/project_version_detail.html new file mode 100644 index 000000000..8e4eee05b --- /dev/null +++ b/readthedocs/templates/projects/project_version_detail.html @@ -0,0 +1,23 @@ +{% extends "projects/base_project.html" %} + +{% load i18n %} + +{% block title %}{{ version.name }}{% endblock %} + + +{% block project_editing %} + {% with versions_active="active" %} + {% include "core/project_bar.html" %} + {% endwith %} +{% endblock %} + +{% block content-header %}

{% blocktrans with version.name as version_name %}{{ version_name }}{% endblocktrans %}

{% endblock %} + +{% block content %} +

{{ version.slug }}

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/readthedocs/templates/projects/project_versions.html b/readthedocs/templates/projects/project_versions.html index a80cecf57..b84e8848b 100644 --- a/readthedocs/templates/projects/project_versions.html +++ b/readthedocs/templates/projects/project_versions.html @@ -13,22 +13,40 @@ {% trans "If you are having trouble building older versions of your Documentation, then you can" %} {% trans "Upload a Zip file of HTML" %} {% trans "instead. This is to support versions that may not build anymore with current Sphinx, or to support plugins and extensions that we may not have on our server." %}

-

- {% trans "Choose which versions you would like to publish besides the latest revision." %} -

{% csrf_token %} {% for field in form %} + {% if forloop.first %} - {# For choosing the default version #} -
{{ field.label }}
+ {# This is a custom form listing the possible active versions, to make 1 the default #} +

{{ field.label }}

{{ field }} +

+ {% trans "Choose the version that / will redirect to." %} +

+ {% else %} - {% if field.label != "privacy" %} -

{{ field.label}}

+ + {% if forloop.counter0 == 1 %} +

{% trans "Choose Active Verisons" %}

+

+ {% trans "Active versions below will show up on the site." %} +

+ {% endif %} + + + {% if field.label == "privacy" %} {{ field }} + + {% comment %} + {% elif field.label == "tags" %} + Tags: {{ field }} + {% endcomment %} + {% else %} - {{ field }} + {# This is a custom field with a label of the version, and a value of a checkbox denoting if it is active #} +

{{ field.label}}

+ Active {{ field }} {% endif %} {% endif %}