Merge branch 'auth'
Conflicts: readthedocs/builds/filters.py readthedocs/templates/builds/build_list.htmlrtd2
commit
670ffe4ad4
|
@ -28,6 +28,7 @@ Site Documentation
|
|||
faq
|
||||
support
|
||||
features
|
||||
privacy
|
||||
webhooks
|
||||
alternate_domains
|
||||
sponsors
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
Privacy Levels
|
||||
==============
|
||||
|
||||
Read the Docs supports 3 different privacy levels on 2 different objects;
|
||||
Public, Protected, Private on Projects and Versions.
|
||||
|
||||
Understanding the Privacy Levels
|
||||
--------------------------------
|
||||
|
||||
+------------+------------+-----------+
|
||||
| Level | Detail | Listing |
|
||||
+============+============+===========+
|
||||
| Private | No | No |
|
||||
+------------+------------+-----------+
|
||||
| Protected | Yes | No |
|
||||
+------------+------------+-----------+
|
||||
| Public | Yes | Yes |
|
||||
+------------+------------+-----------+
|
||||
|
||||
Public
|
||||
~~~~~~
|
||||
|
||||
This is the easiest and most obvious. It is also the default. It means that everything is available to be seen by everyone.
|
||||
|
||||
Protected
|
||||
~~~~~~~~~
|
||||
|
||||
Protected means that your object won't show up in Listing Pages, but Detail pages still still work.
|
||||
For example, a Project that is Protected will not show on the homepage Recently Updated list,
|
||||
however, if you link directly to the project, you will get a 200 and the page will display.
|
||||
|
||||
Protected Versions are similar, they won't show up in your version listings, but will be available once linked to.
|
||||
|
||||
|
||||
Private
|
||||
~~~~~~~
|
||||
|
||||
Private objects are available only to people who have permissions so see them.
|
||||
They will not display on any list view, and will 404 when you link them to others.
|
||||
|
||||
Project Objects
|
||||
----------------
|
||||
|
||||
Detail Views
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Project Detail (/projects/<slug>)
|
||||
* API Detail (/api/v1/project/<slug>/)
|
||||
|
||||
List Views
|
||||
~~~~~~~~~~
|
||||
|
||||
* Home Page
|
||||
* All Projects Page
|
||||
* User Profile Page (/profiles/<user>/)
|
||||
|
||||
|
||||
Version Objects
|
||||
----------------
|
||||
|
||||
List Views
|
||||
~~~~~~~~~~
|
||||
|
||||
* Project Detail (/projects/<slug>)
|
||||
* Version Selector on Home page
|
||||
* Version Selector on Documentation page
|
|
@ -1,3 +1,4 @@
|
|||
# Pypi ftw.
|
||||
Unipath==0.2.1
|
||||
bzr==2.5b4
|
||||
celery==3.0.9
|
||||
|
@ -19,6 +20,8 @@ slumber==0.4.2
|
|||
Sphinx==1.1.2
|
||||
unittest-xml-reporting==1.3.1
|
||||
sphinx-http-domain==0.2
|
||||
django-guardian==1.0.4
|
||||
|
||||
# Pegged git requirements
|
||||
git+https://github.com/toastdriven/django-haystack@259274e4127f723d76b893c87a82777f9490b960#egg=django_haystack
|
||||
hg+http://bitbucket.org/andrewgodwin/south/@ecaafda23e600e510e252734d67bf8f9f2362dc9#egg=South-tip
|
||||
|
@ -28,3 +31,4 @@ git+http://github.com/ericflo/django-pagination.git@47e7ec874cd7dddda5ed13ffb699
|
|||
git+http://github.com/nathanborror/django-basic-apps.git@171fdbe21a0dbbb38919a383cc265cb3cbc73771#egg=django_basic_apps-dev
|
||||
git+http://github.com/nathanborror/django-registration.git@dc0b564b7bfb79f58592fe8ad836729a85ec17ae#egg=django_registration-dev
|
||||
git+https://github.com/toastdriven/django-tastypie.git@c5451b90b18b0cb64841b2276d543230d5f58231#egg=django_tastypie-dev
|
||||
|
||||
|
|
|
@ -29,183 +29,15 @@ from djangome import views as djangome
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class SearchMixin(object):
|
||||
'''
|
||||
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 is limited to the model defined by the meta queryset. If the
|
||||
search is invalid, a 400 Bad Request will be raised.
|
||||
|
||||
e.g.
|
||||
class Meta:
|
||||
# Return facet counts for each facetname
|
||||
search_facets = ['facetname1', 'facetname1']
|
||||
|
||||
# Number of results returned per page
|
||||
search_page_size = 20
|
||||
|
||||
# Highlight search terms in the text
|
||||
search_highlight = True
|
||||
'''
|
||||
def get_search(self, request, **kwargs):
|
||||
self.method_check(request, allowed=['get'])
|
||||
self.is_authenticated(request)
|
||||
self.throttle_check(request)
|
||||
object_list = self._search(request,
|
||||
self._meta.queryset.model,
|
||||
facets = getattr(self._meta, 'search_facets', []),
|
||||
page_size = getattr(self._meta, 'search_page_size', 20),
|
||||
highlight = getattr(self._meta, 'search_highlight', True),
|
||||
)
|
||||
self.log_throttled_access(request)
|
||||
return self.create_response(request, object_list)
|
||||
|
||||
def _url_template(self, query, selected_facets):
|
||||
'''
|
||||
Construct a url template to assist with navigating the resources.
|
||||
This looks a bit nasty but urllib.urlencode resulted in even
|
||||
nastier output...
|
||||
'''
|
||||
query_params = []
|
||||
for facet in selected_facets:
|
||||
query_params.append(('selected_facets', facet))
|
||||
query_params += [('q', query), ('format', 'json'), ('page', '{0}')]
|
||||
query_string = '&'.join('='.join(p) for p in query_params)
|
||||
url_template = reverse('api_get_search', kwargs={
|
||||
'resource_name': self._meta.resource_name,
|
||||
'api_name': 'v1'
|
||||
})
|
||||
return url_template + '?' + query_string
|
||||
|
||||
def _search(self, request, model, facets=None, page_size=20, highlight=True):
|
||||
'''
|
||||
`facets`
|
||||
A list of facets to include with the results
|
||||
`models`
|
||||
Limit the search to one or more models
|
||||
'''
|
||||
form = FacetedSearchForm(request.GET, facets=facets or [],
|
||||
models=(model,), load_all=True)
|
||||
if not form.is_valid():
|
||||
return self.error_response({'errors': form.errors }, request)
|
||||
results = form.search()
|
||||
|
||||
paginator = Paginator(results, page_size)
|
||||
try:
|
||||
page = paginator.page(int(request.GET.get('page', 1)))
|
||||
except InvalidPage:
|
||||
raise Http404(ugettext("Sorry, no results on that page."))
|
||||
|
||||
objects = []
|
||||
query = request.GET.get('q', '')
|
||||
highlighter = Highlighter(query)
|
||||
for result in page.object_list:
|
||||
if not result:
|
||||
continue
|
||||
text = result.text
|
||||
if highlight:
|
||||
text = highlighter.highlight(text)
|
||||
bundle = self.build_bundle(obj=result.object, request=request)
|
||||
bundle = self.full_dehydrate(bundle)
|
||||
bundle.data['text'] = text
|
||||
objects.append(bundle)
|
||||
|
||||
url_template = self._url_template(query, form['selected_facets'].value())
|
||||
page_data = {
|
||||
'number': page.number,
|
||||
'per_page': paginator.per_page,
|
||||
'num_pages': paginator.num_pages,
|
||||
'page_range': paginator.page_range,
|
||||
'object_count': paginator.count,
|
||||
'url_template': url_template,
|
||||
}
|
||||
if page.has_next():
|
||||
page_data['url_next'] = url_template.format(page.next_page_number())
|
||||
if page.has_previous():
|
||||
page_data['url_prev'] = url_template.format(page.previous_page_number())
|
||||
|
||||
object_list = {
|
||||
'page': page_data,
|
||||
'objects': objects,
|
||||
}
|
||||
if facets:
|
||||
object_list.update({'facets': results.facet_counts()})
|
||||
return object_list
|
||||
|
||||
|
||||
# XXX: This method is available in the latest tastypie, remove
|
||||
# once available in production.
|
||||
def error_response(self, errors, request):
|
||||
if request:
|
||||
desired_format = self.determine_format(request)
|
||||
else:
|
||||
desired_format = self._meta.default_format
|
||||
serialized = self.serialize(request, errors, desired_format)
|
||||
response = http.HttpBadRequest(content=serialized, content_type=build_content_type(desired_format))
|
||||
raise ImmediateHttpResponse(response=response)
|
||||
|
||||
|
||||
class PostAuthentication(BasicAuthentication):
|
||||
def is_authenticated(self, request, **kwargs):
|
||||
if request.method == "GET":
|
||||
return True
|
||||
return super(PostAuthentication, self).is_authenticated(request, **kwargs)
|
||||
|
||||
|
||||
class EnhancedModelResource(ModelResource):
|
||||
def obj_get_list(self, request=None, **kwargs):
|
||||
"""
|
||||
A ORM-specific implementation of ``obj_get_list``.
|
||||
|
||||
Takes an optional ``request`` object, whose ``GET`` dictionary can be
|
||||
used to narrow the query.
|
||||
"""
|
||||
filters = None
|
||||
|
||||
if hasattr(request, 'GET'):
|
||||
filters = request.GET
|
||||
|
||||
applicable_filters = self.build_filters(filters=filters)
|
||||
applicable_filters.update(kwargs)
|
||||
|
||||
try:
|
||||
return self.get_object_list(request).filter(**applicable_filters)
|
||||
except ValueError, e:
|
||||
raise NotFound(ugettext("Invalid resource lookup data provided (mismatched type).: %(error)s") % {'error': e})
|
||||
|
||||
|
||||
class UserResource(ModelResource):
|
||||
class Meta:
|
||||
allowed_methods = ['get']
|
||||
queryset = User.objects.all()
|
||||
fields = ['username', 'first_name', 'last_name', 'last_login', 'id']
|
||||
filtering = {
|
||||
'username': 'exact',
|
||||
}
|
||||
|
||||
def override_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
|
||||
]
|
||||
|
||||
class OwnerAuthorization(Authorization):
|
||||
def apply_limits(self, request, object_list):
|
||||
if request and hasattr(request, 'user') and request.method != 'GET':
|
||||
if request.user.is_authenticated():
|
||||
object_list = object_list.filter(users__in=[request.user])
|
||||
else:
|
||||
object_list = object_list.none()
|
||||
|
||||
return object_list
|
||||
from .utils import SearchMixin, PostAuthentication, EnhancedModelResource
|
||||
|
||||
class ProjectResource(ModelResource, SearchMixin):
|
||||
users = fields.ToManyField(UserResource, 'users')
|
||||
users = fields.ToManyField('api.base.UserResource', 'users')
|
||||
|
||||
class Meta:
|
||||
include_absolute_url = True
|
||||
allowed_methods = ['get', 'post', 'put']
|
||||
queryset = Project.objects.all()
|
||||
queryset = Project.objects.public()
|
||||
authentication = PostAuthentication()
|
||||
authorization = DjangoAuthorization()
|
||||
excludes = ['path', 'featured']
|
||||
|
@ -214,6 +46,10 @@ class ProjectResource(ModelResource, SearchMixin):
|
|||
"slug": ALL_WITH_RELATIONS,
|
||||
}
|
||||
|
||||
def get_object_list(self, request):
|
||||
self._meta.queryset = Project.objects.public(user=request.user)
|
||||
return super(ProjectResource, self).get_object_list(request)
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
bundle.data['subdomain'] = "http://%s/" % bundle.obj.subdomain
|
||||
return bundle
|
||||
|
@ -244,29 +80,6 @@ class ProjectResource(ModelResource, SearchMixin):
|
|||
]
|
||||
|
||||
|
||||
class BuildResource(EnhancedModelResource):
|
||||
project = fields.ForeignKey(ProjectResource, 'project')
|
||||
version = fields.ForeignKey('api.base.VersionResource', 'version')
|
||||
|
||||
class Meta:
|
||||
include_absolute_url = True
|
||||
allowed_methods = ['get', 'post', 'put']
|
||||
queryset = Build.objects.all()
|
||||
authentication = PostAuthentication()
|
||||
authorization = DjangoAuthorization()
|
||||
filtering = {
|
||||
"project": ALL_WITH_RELATIONS,
|
||||
"slug": ALL_WITH_RELATIONS,
|
||||
"type": ALL_WITH_RELATIONS,
|
||||
"state": ALL_WITH_RELATIONS,
|
||||
}
|
||||
|
||||
def override_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_list'), name="build_list_detail"),
|
||||
]
|
||||
|
||||
class VersionResource(EnhancedModelResource):
|
||||
project = fields.ForeignKey(ProjectResource, 'project', full=True)
|
||||
|
||||
|
@ -274,7 +87,7 @@ class VersionResource(EnhancedModelResource):
|
|||
queryset = Version.objects.all()
|
||||
allowed_methods = ['get', 'put', 'post']
|
||||
always_return_data = True
|
||||
queryset = Version.objects.all()
|
||||
queryset = Version.objects.public()
|
||||
authentication = PostAuthentication()
|
||||
authorization = DjangoAuthorization()
|
||||
filtering = {
|
||||
|
@ -288,6 +101,10 @@ class VersionResource(EnhancedModelResource):
|
|||
#bundle.data['subdomain'] = "http://%s/en/%s/" % (bundle.obj.project.subdomain, bundle.obj.slug)
|
||||
#return bundle
|
||||
|
||||
def get_object_list(self, request):
|
||||
self._meta.queryset = Version.objects.public(user=request.user)
|
||||
return super(VersionResource, self).get_object_list(request)
|
||||
|
||||
def version_compare(self, request, **kwargs):
|
||||
project = get_object_or_404(Project, slug=kwargs['project_slug'])
|
||||
highest = highest_version(project.versions.filter(active=True))
|
||||
|
@ -331,6 +148,28 @@ class VersionResource(EnhancedModelResource):
|
|||
url(r"^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+)/(?P<version_slug>[a-z0-9-_.]+)/build/$" % self._meta.resource_name, self.wrap_view('build_version'), name="api_version_build_slug"),
|
||||
]
|
||||
|
||||
class BuildResource(EnhancedModelResource):
|
||||
project = fields.ForeignKey(ProjectResource, 'project')
|
||||
version = fields.ForeignKey('api.base.VersionResource', 'version')
|
||||
|
||||
class Meta:
|
||||
include_absolute_url = True
|
||||
allowed_methods = ['get', 'post', 'put']
|
||||
queryset = Build.objects.all()
|
||||
authentication = PostAuthentication()
|
||||
authorization = DjangoAuthorization()
|
||||
filtering = {
|
||||
"project": ALL_WITH_RELATIONS,
|
||||
"slug": ALL_WITH_RELATIONS,
|
||||
"type": ALL_WITH_RELATIONS,
|
||||
"state": ALL_WITH_RELATIONS,
|
||||
}
|
||||
|
||||
def override_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_list'), name="build_list_detail"),
|
||||
]
|
||||
|
||||
class FileResource(EnhancedModelResource, SearchMixin):
|
||||
project = fields.ForeignKey(ProjectResource, 'project', full=True)
|
||||
|
@ -376,3 +215,19 @@ class FileResource(EnhancedModelResource, SearchMixin):
|
|||
|
||||
self.log_throttled_access(request)
|
||||
return self.create_response(request, object_list)
|
||||
|
||||
|
||||
class UserResource(ModelResource):
|
||||
class Meta:
|
||||
allowed_methods = ['get']
|
||||
queryset = User.objects.all()
|
||||
fields = ['username', 'first_name', 'last_name', 'last_login', 'id']
|
||||
filtering = {
|
||||
'username': 'exact',
|
||||
}
|
||||
|
||||
def override_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
import logging
|
||||
|
||||
from django.core.paginator import Paginator, InvalidPage
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf.urls.defaults import url
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import Http404
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext
|
||||
|
||||
from haystack.utils import Highlighter
|
||||
from tastypie import fields
|
||||
from tastypie.authentication import BasicAuthentication
|
||||
from tastypie.authorization import Authorization, DjangoAuthorization
|
||||
from tastypie.constants import ALL_WITH_RELATIONS, ALL
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.exceptions import NotFound, ImmediateHttpResponse
|
||||
from tastypie import http
|
||||
from tastypie.utils.mime import build_content_type
|
||||
from tastypie.http import HttpCreated
|
||||
from tastypie.utils import dict_strip_unicode_keys, trailing_slash
|
||||
|
||||
from core.forms import FacetedSearchForm
|
||||
from builds.models import Build, Version
|
||||
from projects.models import Project, ImportedFile
|
||||
from projects.utils import highest_version, mkversion
|
||||
from projects import tasks
|
||||
from djangome import views as djangome
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class SearchMixin(object):
|
||||
'''
|
||||
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 is limited to the model defined by the meta queryset. If the
|
||||
search is invalid, a 400 Bad Request will be raised.
|
||||
|
||||
e.g.
|
||||
class Meta:
|
||||
# Return facet counts for each facetname
|
||||
search_facets = ['facetname1', 'facetname1']
|
||||
|
||||
# Number of results returned per page
|
||||
search_page_size = 20
|
||||
|
||||
# Highlight search terms in the text
|
||||
search_highlight = True
|
||||
'''
|
||||
def get_search(self, request, **kwargs):
|
||||
self.method_check(request, allowed=['get'])
|
||||
self.is_authenticated(request)
|
||||
self.throttle_check(request)
|
||||
object_list = self._search(request,
|
||||
self._meta.queryset.model,
|
||||
facets = getattr(self._meta, 'search_facets', []),
|
||||
page_size = getattr(self._meta, 'search_page_size', 20),
|
||||
highlight = getattr(self._meta, 'search_highlight', True),
|
||||
)
|
||||
self.log_throttled_access(request)
|
||||
return self.create_response(request, object_list)
|
||||
|
||||
def _url_template(self, query, selected_facets):
|
||||
'''
|
||||
Construct a url template to assist with navigating the resources.
|
||||
This looks a bit nasty but urllib.urlencode resulted in even
|
||||
nastier output...
|
||||
'''
|
||||
query_params = []
|
||||
for facet in selected_facets:
|
||||
query_params.append(('selected_facets', facet))
|
||||
query_params += [('q', query), ('format', 'json'), ('page', '{0}')]
|
||||
query_string = '&'.join('='.join(p) for p in query_params)
|
||||
url_template = reverse('api_get_search', kwargs={
|
||||
'resource_name': self._meta.resource_name,
|
||||
'api_name': 'v1'
|
||||
})
|
||||
return url_template + '?' + query_string
|
||||
|
||||
def _search(self, request, model, facets=None, page_size=20, highlight=True):
|
||||
'''
|
||||
`facets`
|
||||
A list of facets to include with the results
|
||||
`models`
|
||||
Limit the search to one or more models
|
||||
'''
|
||||
form = FacetedSearchForm(request.GET, facets=facets or [],
|
||||
models=(model,), load_all=True)
|
||||
if not form.is_valid():
|
||||
return self.error_response({'errors': form.errors }, request)
|
||||
results = form.search()
|
||||
|
||||
paginator = Paginator(results, page_size)
|
||||
try:
|
||||
page = paginator.page(int(request.GET.get('page', 1)))
|
||||
except InvalidPage:
|
||||
raise Http404(ugettext("Sorry, no results on that page."))
|
||||
|
||||
objects = []
|
||||
query = request.GET.get('q', '')
|
||||
highlighter = Highlighter(query)
|
||||
for result in page.object_list:
|
||||
if not result:
|
||||
continue
|
||||
text = result.text
|
||||
if highlight:
|
||||
text = highlighter.highlight(text)
|
||||
bundle = self.build_bundle(obj=result.object, request=request)
|
||||
bundle = self.full_dehydrate(bundle)
|
||||
bundle.data['text'] = text
|
||||
objects.append(bundle)
|
||||
|
||||
url_template = self._url_template(query, form['selected_facets'].value())
|
||||
page_data = {
|
||||
'number': page.number,
|
||||
'per_page': paginator.per_page,
|
||||
'num_pages': paginator.num_pages,
|
||||
'page_range': paginator.page_range,
|
||||
'object_count': paginator.count,
|
||||
'url_template': url_template,
|
||||
}
|
||||
if page.has_next():
|
||||
page_data['url_next'] = url_template.format(page.next_page_number())
|
||||
if page.has_previous():
|
||||
page_data['url_prev'] = url_template.format(page.previous_page_number())
|
||||
|
||||
object_list = {
|
||||
'page': page_data,
|
||||
'objects': objects,
|
||||
}
|
||||
if facets:
|
||||
object_list.update({'facets': results.facet_counts()})
|
||||
return object_list
|
||||
|
||||
|
||||
# XXX: This method is available in the latest tastypie, remove
|
||||
# once available in production.
|
||||
def error_response(self, errors, request):
|
||||
if request:
|
||||
desired_format = self.determine_format(request)
|
||||
else:
|
||||
desired_format = self._meta.default_format
|
||||
serialized = self.serialize(request, errors, desired_format)
|
||||
response = http.HttpBadRequest(content=serialized, content_type=build_content_type(desired_format))
|
||||
raise ImmediateHttpResponse(response=response)
|
||||
|
||||
|
||||
class PostAuthentication(BasicAuthentication):
|
||||
def is_authenticated(self, request, **kwargs):
|
||||
if request.method == "GET":
|
||||
return True
|
||||
return super(PostAuthentication, self).is_authenticated(request, **kwargs)
|
||||
|
||||
|
||||
class EnhancedModelResource(ModelResource):
|
||||
def obj_get_list(self, request=None, **kwargs):
|
||||
"""
|
||||
A ORM-specific implementation of ``obj_get_list``.
|
||||
|
||||
Takes an optional ``request`` object, whose ``GET`` dictionary can be
|
||||
used to narrow the query.
|
||||
"""
|
||||
filters = None
|
||||
|
||||
if hasattr(request, 'GET'):
|
||||
filters = request.GET
|
||||
|
||||
applicable_filters = self.build_filters(filters=filters)
|
||||
applicable_filters.update(kwargs)
|
||||
|
||||
try:
|
||||
return self.get_object_list(request).filter(**applicable_filters)
|
||||
except ValueError, e:
|
||||
raise NotFound(ugettext("Invalid resource lookup data provided (mismatched type).: %(error)s") % {'error': e})
|
||||
|
||||
|
||||
class OwnerAuthorization(Authorization):
|
||||
def apply_limits(self, request, object_list):
|
||||
if request and hasattr(request, 'user') and request.method != 'GET':
|
||||
if request.user.is_authenticated():
|
||||
object_list = object_list.filter(users__in=[request.user])
|
||||
else:
|
||||
object_list = object_list.none()
|
||||
|
||||
return object_list
|
|
@ -3,10 +3,15 @@
|
|||
|
||||
from django.contrib import admin
|
||||
from builds.models import Build, VersionAlias, Version
|
||||
from guardian.admin import GuardedModelAdmin
|
||||
|
||||
class BuildAdmin(admin.ModelAdmin):
|
||||
list_display = ('project', 'date', 'success', 'type', 'state')
|
||||
|
||||
class VersionAdmin(GuardedModelAdmin):
|
||||
search_fields = ('slug', 'project__name')
|
||||
list_filter = ('project',)
|
||||
|
||||
admin.site.register(Build, BuildAdmin)
|
||||
admin.site.register(VersionAlias)
|
||||
admin.site.register(Version)
|
||||
admin.site.register(Version, VersionAdmin)
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'Version.privacy_level'
|
||||
db.add_column('builds_version', 'privacy_level',
|
||||
self.gf('django.db.models.fields.CharField')(default='public', max_length=20),
|
||||
keep_default=False)
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Version.privacy_level'
|
||||
db.delete_column('builds_version', 'privacy_level')
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 6, 898344)'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 6, 898075)'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'builds.build': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Build'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'error': ('django.db.models.fields.TextField', [], {}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'output': ('django.db.models.fields.TextField', [], {}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'builds'", 'to': "orm['projects.Project']"}),
|
||||
'setup': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'setup_error': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'state': ('django.db.models.fields.CharField', [], {'default': "'finished'", 'max_length': '55'}),
|
||||
'success': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'default': "'html'", 'max_length': '55'}),
|
||||
'version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'builds'", 'null': 'True', 'to': "orm['builds.Version']"})
|
||||
},
|
||||
'builds.version': {
|
||||
'Meta': {'ordering': "['-verbose_name']", 'unique_together': "[('project', 'slug')]", 'object_name': 'Version'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'built': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'versions'", 'to': "orm['projects.Project']"}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'uploaded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'verbose_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'builds.versionalias': {
|
||||
'Meta': {'object_name': 'VersionAlias'},
|
||||
'from_slug': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'largest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'aliases'", 'to': "orm['projects.Project']"}),
|
||||
'to_slug': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'projects.project': {
|
||||
'Meta': {'ordering': "('slug',)", 'object_name': 'Project'},
|
||||
'analytics_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'conf_py_file': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
|
||||
'copyright': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'crate_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'default_branch': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'default_version': ('django.db.models.fields.CharField', [], {'default': "'latest'", 'max_length': '255'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'django_packages_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'documentation_type': ('django.db.models.fields.CharField', [], {'default': "'sphinx'", 'max_length': '20'}),
|
||||
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
|
||||
'project_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'pub_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'related_projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['projects.Project']", 'null': 'True', 'through': "orm['projects.ProjectRelationship']", 'blank': 'True'}),
|
||||
'repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'repo_type': ('django.db.models.fields.CharField', [], {'default': "'git'", 'max_length': '10'}),
|
||||
'requirements_file': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'skip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'suffix': ('django.db.models.fields.CharField', [], {'default': "'.rst'", 'max_length': '10'}),
|
||||
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}),
|
||||
'use_system_packages': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'use_virtualenv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'version_privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'})
|
||||
},
|
||||
'projects.projectrelationship': {
|
||||
'Meta': {'object_name': 'ProjectRelationship'},
|
||||
'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'superprojects'", 'to': "orm['projects.Project']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subprojects'", 'to': "orm['projects.Project']"})
|
||||
},
|
||||
'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
|
||||
},
|
||||
'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
|
||||
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['builds']
|
|
@ -1,10 +1,41 @@
|
|||
from django.db import models
|
||||
from projects.models import Project
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||
|
||||
from guardian.shortcuts import assign, get_objects_for_user
|
||||
|
||||
from projects.models import Project
|
||||
from projects import constants
|
||||
from .constants import BUILD_STATE, BUILD_TYPES
|
||||
|
||||
|
||||
class VersionManager(models.Manager):
|
||||
def _filter_queryset(self, user, project, privacy_level):
|
||||
if isinstance(privacy_level, basestring):
|
||||
privacy_level = (privacy_level,)
|
||||
queryset = Version.objects.filter(privacy_level__in=privacy_level)
|
||||
if not user and not project:
|
||||
return queryset
|
||||
if user and user.is_authenticated():
|
||||
# Add in possible user-specific views
|
||||
user_queryset = get_objects_for_user(user, 'builds.view_version')
|
||||
queryset = user_queryset | queryset
|
||||
if project:
|
||||
# Filter by project if requested
|
||||
queryset = queryset.filter(project=project)
|
||||
return queryset.filter(active=True)
|
||||
|
||||
def public(self, user=None, project=None, *args, **kwargs):
|
||||
queryset = self._filter_queryset(user, project, privacy_level=constants.PUBLIC)
|
||||
return queryset.filter(*args, **kwargs)
|
||||
|
||||
def protected(self, user=None, project=None, *args, **kwargs):
|
||||
queryset = self._filter_queryset(user, project, privacy_level=(constants.PUBLIC, constants.PROTECTED))
|
||||
return queryset.filter(*args, **kwargs)
|
||||
|
||||
def private(self, user=None, project=None, *args, **kwargs):
|
||||
queryset = self._filter_queryset(user, project, privacy_level=constants.PRIVATE)
|
||||
return queryset.filter(*args, **kwargs)
|
||||
|
||||
class Version(models.Model):
|
||||
project = models.ForeignKey(Project, verbose_name=_('Project'), related_name='versions')
|
||||
identifier = models.CharField(_('Identifier'), max_length=255) # used by the vcs backend
|
||||
|
@ -13,10 +44,19 @@ class Version(models.Model):
|
|||
active = models.BooleanField(_('Active'), default=False)
|
||||
built = models.BooleanField(_('Built'), default=False)
|
||||
uploaded = models.BooleanField(_('Uploaded'), default=False)
|
||||
privacy_level = models.CharField(_('Privacy Level'), max_length=20,
|
||||
choices=constants.PRIVACY_CHOICES, default='public',
|
||||
help_text=_("Level of privacy for this Version."))
|
||||
|
||||
objects = VersionManager()
|
||||
|
||||
class Meta:
|
||||
unique_together = [('project', 'slug')]
|
||||
ordering = ['-verbose_name']
|
||||
permissions = (
|
||||
# Translators: Permission around whether a user can view the version
|
||||
('view_version', _('View Version')),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return ugettext(u"Version %(version)s of %(project)s (%(pk)s)" % {
|
||||
|
@ -30,6 +70,16 @@ class Version(models.Model):
|
|||
return ''
|
||||
return self.project.get_docs_url(version_slug=self.slug)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Add permissions to the Version for all owners on save.
|
||||
"""
|
||||
obj = super(Version, self).save(*args, **kwargs)
|
||||
for owner in self.project.users.all():
|
||||
assign('view_version', owner, self)
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
class VersionAlias(models.Model):
|
||||
project = models.ForeignKey(Project, verbose_name=_('Project'), related_name='aliases')
|
||||
|
|
|
@ -3,16 +3,18 @@ from django.http import HttpResponsePermanentRedirect
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic.list_detail import object_list, object_detail
|
||||
|
||||
from guardian.decorators import permission_required_or_403
|
||||
from taggit.models import Tag
|
||||
|
||||
from builds.models import Build
|
||||
from builds.filters import BuildFilter
|
||||
from projects.models import Project
|
||||
|
||||
from taggit.models import Tag
|
||||
|
||||
def build_list(request, project_slug=None, tag=None):
|
||||
"""Show a list of builds.
|
||||
"""
|
||||
queryset = Build.objects.all()
|
||||
filter = BuildFilter(request.GET, queryset=queryset)
|
||||
|
||||
if tag:
|
||||
tag = get_object_or_404(Tag, slug=tag)
|
||||
|
@ -20,7 +22,7 @@ def build_list(request, project_slug=None, tag=None):
|
|||
else:
|
||||
tag = None
|
||||
|
||||
project = get_object_or_404(Project, slug=project_slug)
|
||||
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
|
||||
queryset = queryset.filter(project=project)
|
||||
filter = BuildFilter(request.GET, queryset=queryset)
|
||||
active_builds = queryset.exclude(state="finished").values('id')
|
||||
|
@ -40,8 +42,8 @@ def build_list(request, project_slug=None, tag=None):
|
|||
def build_detail(request, project_slug, pk):
|
||||
"""Show the details of a particular build.
|
||||
"""
|
||||
project = get_object_or_404(Project, slug=project_slug)
|
||||
queryset = Build.objects.filter(project=project)
|
||||
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
|
||||
|
||||
return object_detail(
|
||||
request,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_active": true,
|
||||
"is_superuser": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": true,
|
||||
"last_login": "2010-08-14 01:51:05",
|
||||
"groups": [],
|
||||
|
@ -34,5 +34,23 @@
|
|||
"email": "e@etest.co",
|
||||
"date_joined": "2010-08-14 01:50:58"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "auth.user",
|
||||
"fields": {
|
||||
"username": "super",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_active": true,
|
||||
"is_superuser": true,
|
||||
"is_staff": true,
|
||||
"last_login": "2010-08-14 01:51:05",
|
||||
"groups": [],
|
||||
"user_permissions": [],
|
||||
"password": "sha1$035cb$156ad6cb44332fb4f24bcb634142a67435be0b37",
|
||||
"email": "e@e.co",
|
||||
"date_joined": "2010-08-14 01:50:58"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import logging
|
||||
import getpass
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -49,3 +48,5 @@ def run_on_app_servers(command):
|
|||
else:
|
||||
ret = os.system(command)
|
||||
return ret
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.views.static import serve
|
|||
from django.views.generic import TemplateView
|
||||
|
||||
from haystack.query import EmptySearchQuerySet
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
|
||||
from builds.models import Build
|
||||
from core.forms import FacetedSearchForm
|
||||
|
@ -31,11 +32,11 @@ log = logging.getLogger(__name__)
|
|||
|
||||
def homepage(request):
|
||||
#latest_projects = Project.objects.filter(builds__isnull=False).annotate(max_date=Max('builds__date')).order_by('-max_date')[:10]
|
||||
latest_projects = Project.objects.order_by('-modified_date')[:10]
|
||||
latest = Project.objects.public(request.user).order_by('-modified_date')[:10]
|
||||
featured = Project.objects.filter(featured=True)
|
||||
|
||||
return render_to_response('homepage.html',
|
||||
{'project_list': latest_projects,
|
||||
{'project_list': latest,
|
||||
'featured_list': featured,
|
||||
#'updated_list': updated
|
||||
},
|
||||
|
@ -218,7 +219,7 @@ def subproject_serve_docs(request, project_slug, lang_slug=None, version_slug=No
|
|||
'filename': filename
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
if subproject_qs.exists():
|
||||
return serve_docs(request, lang_slug, version_slug, filename, project_slug)
|
||||
else:
|
||||
|
|
|
@ -5,6 +5,7 @@ and related models.
|
|||
from builds.models import Version
|
||||
from django.contrib import admin
|
||||
from projects.models import Project, ImportedFile, ProjectRelationship
|
||||
from guardian.admin import GuardedModelAdmin
|
||||
|
||||
class ProjectRelationshipInline(admin.TabularInline):
|
||||
model = ProjectRelationship
|
||||
|
@ -14,7 +15,7 @@ class VersionInline(admin.TabularInline):
|
|||
model = Version
|
||||
|
||||
|
||||
class ProjectAdmin(admin.ModelAdmin):
|
||||
class ProjectAdmin(GuardedModelAdmin):
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
list_display = ('name', 'repo', 'repo_type', 'featured', 'theme')
|
||||
list_filter = ('repo_type', 'featured')
|
||||
|
|
|
@ -70,3 +70,14 @@ REPO_CHOICES = (
|
|||
('hg', _('Mercurial')),
|
||||
('bzr', _('Bazaar')),
|
||||
)
|
||||
|
||||
PUBLIC = 'public'
|
||||
PROTECTED = 'protected'
|
||||
PRIVATE = 'private'
|
||||
|
||||
PRIVACY_CHOICES = (
|
||||
(PUBLIC, _('Public')),
|
||||
(PROTECTED, _('Protected')),
|
||||
(PRIVATE, _('Private')),
|
||||
)
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ from django.conf import settings
|
|||
from django.template.defaultfilters import slugify
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from projects import constants
|
||||
from projects.models import Project
|
||||
from projects.tasks import update_docs
|
||||
|
||||
|
@ -26,10 +29,18 @@ class ImportProjectForm(ProjectForm):
|
|||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ('name', 'repo', 'repo_type', 'description', 'project_url',
|
||||
'tags', 'default_branch', 'default_version', 'use_virtualenv',
|
||||
'use_system_packages', 'conf_py_file', 'requirements_file',
|
||||
'analytics_code', 'documentation_type')
|
||||
fields = (
|
||||
# Important
|
||||
'name', 'repo', 'repo_type', 'description',
|
||||
# Not as important
|
||||
'project_url', 'tags', 'default_branch', 'default_version', 'conf_py_file',
|
||||
# Privacy
|
||||
'privacy_level', 'version_privacy_level',
|
||||
# Python specific
|
||||
'use_virtualenv', 'use_system_packages', 'requirements_file',
|
||||
# Fringe
|
||||
'analytics_code', 'documentation_type', 'tags'
|
||||
)
|
||||
|
||||
def clean_repo(self):
|
||||
repo = self.cleaned_data.get('repo', '').strip()
|
||||
|
@ -63,7 +74,7 @@ class DualCheckboxWidget(forms.CheckboxInput):
|
|||
def render(self, name, value, attrs=None):
|
||||
checkbox = super(DualCheckboxWidget, self).render(name, value, attrs)
|
||||
icon = self.render_icon()
|
||||
return u'%s%s' % (checkbox, icon)
|
||||
return mark_safe(u'%s%s' % (checkbox, icon))
|
||||
|
||||
def render_icon(self):
|
||||
context = {
|
||||
|
@ -87,9 +98,11 @@ class BaseVersionsForm(forms.Form):
|
|||
|
||||
def save_version(self, version):
|
||||
new_value = self.cleaned_data.get('version-%s' % version.slug, None)
|
||||
if new_value is None or new_value == version.active:
|
||||
privacy_level = self.cleaned_data.get('privacy-%s' % version.slug, None)
|
||||
if (new_value is None or new_value == version.active) and (privacy_level is None or privacy_level == version.privacy_level):
|
||||
return
|
||||
version.active = new_value
|
||||
version.privacy_level = privacy_level
|
||||
version.save()
|
||||
if version.active and not version.built and not version.uploaded:
|
||||
update_docs.delay(self.project.pk, record=True, version_pk=version.pk)
|
||||
|
@ -104,18 +117,25 @@ def build_versions_form(project):
|
|||
if active.exists():
|
||||
choices = [(version.slug, version.verbose_name) for version in active]
|
||||
attrs['default-version'] = forms.ChoiceField(
|
||||
label=_("Choose the default version for this project"),
|
||||
label=_("Default Version"),
|
||||
choices=choices,
|
||||
initial=project.get_default_version(),
|
||||
)
|
||||
for version in versions_qs:
|
||||
field_name = 'version-%s' % version.slug
|
||||
privacy_name = 'privacy-%s' % version.slug
|
||||
attrs[field_name] = forms.BooleanField(
|
||||
label=version.verbose_name,
|
||||
widget=DualCheckboxWidget(version),
|
||||
initial=version.active,
|
||||
required=False,
|
||||
)
|
||||
attrs[privacy_name] = forms.ChoiceField(
|
||||
# This isn't a real label, but just a slug for the template
|
||||
label="privacy",
|
||||
choices=constants.PRIVACY_CHOICES,
|
||||
initial=version.privacy_level,
|
||||
)
|
||||
return type('VersionsForm', (BaseVersionsForm,), attrs)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Deleting model 'FileRevision'
|
||||
db.delete_table('projects_filerevision')
|
||||
|
||||
# Deleting model 'File'
|
||||
db.delete_table('projects_file')
|
||||
|
||||
# Adding field 'Project.privacy_level'
|
||||
db.add_column('projects_project', 'privacy_level',
|
||||
self.gf('django.db.models.fields.CharField')(default='public', max_length=20),
|
||||
keep_default=False)
|
||||
|
||||
def backwards(self, orm):
|
||||
# Adding model 'FileRevision'
|
||||
db.create_table('projects_filerevision', (
|
||||
('comment', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
('revision_number', self.gf('django.db.models.fields.IntegerField')()),
|
||||
('file', self.gf('django.db.models.fields.related.ForeignKey')(related_name='revisions', to=orm['projects.File'])),
|
||||
('created_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('diff', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('is_reverted', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
))
|
||||
db.send_create_signal('projects', ['FileRevision'])
|
||||
|
||||
# Adding model 'File'
|
||||
db.create_table('projects_file', (
|
||||
('content', self.gf('django.db.models.fields.TextField')()),
|
||||
('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='files', to=orm['projects.Project'])),
|
||||
('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)),
|
||||
('status', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=1)),
|
||||
('denormalized_path', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||
('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='children', null=True, to=orm['projects.File'], blank=True)),
|
||||
('ordering', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=1)),
|
||||
('heading', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
))
|
||||
db.send_create_signal('projects', ['File'])
|
||||
|
||||
# Deleting field 'Project.privacy_level'
|
||||
db.delete_column('projects_project', 'privacy_level')
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 22, 28, 56, 789520)'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 22, 28, 56, 789253)'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'projects.importedfile': {
|
||||
'Meta': {'object_name': 'ImportedFile'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'md5': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'imported_files'", 'to': "orm['projects.Project']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
|
||||
},
|
||||
'projects.project': {
|
||||
'Meta': {'ordering': "('slug',)", 'object_name': 'Project'},
|
||||
'analytics_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'conf_py_file': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
|
||||
'copyright': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'crate_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'default_branch': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'default_version': ('django.db.models.fields.CharField', [], {'default': "'latest'", 'max_length': '255'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'django_packages_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'documentation_type': ('django.db.models.fields.CharField', [], {'default': "'sphinx'", 'max_length': '20'}),
|
||||
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
|
||||
'project_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'pub_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'related_projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['projects.Project']", 'null': 'True', 'through': "orm['projects.ProjectRelationship']", 'blank': 'True'}),
|
||||
'repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'repo_type': ('django.db.models.fields.CharField', [], {'default': "'git'", 'max_length': '10'}),
|
||||
'requirements_file': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'skip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'suffix': ('django.db.models.fields.CharField', [], {'default': "'.rst'", 'max_length': '10'}),
|
||||
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}),
|
||||
'use_system_packages': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'use_virtualenv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
|
||||
},
|
||||
'projects.projectrelationship': {
|
||||
'Meta': {'object_name': 'ProjectRelationship'},
|
||||
'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'superprojects'", 'to': "orm['projects.Project']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subprojects'", 'to': "orm['projects.Project']"})
|
||||
},
|
||||
'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
|
||||
},
|
||||
'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
|
||||
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['projects']
|
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'Project.version_privacy_level'
|
||||
db.add_column('projects_project', 'version_privacy_level',
|
||||
self.gf('django.db.models.fields.CharField')(default='public', max_length=20),
|
||||
keep_default=False)
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Project.version_privacy_level'
|
||||
db.delete_column('projects_project', 'version_privacy_level')
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 17, 885486)'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 17, 885212)'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'projects.importedfile': {
|
||||
'Meta': {'object_name': 'ImportedFile'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'md5': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'imported_files'", 'to': "orm['projects.Project']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
|
||||
},
|
||||
'projects.project': {
|
||||
'Meta': {'ordering': "('slug',)", 'object_name': 'Project'},
|
||||
'analytics_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'conf_py_file': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
|
||||
'copyright': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'crate_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'default_branch': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'default_version': ('django.db.models.fields.CharField', [], {'default': "'latest'", 'max_length': '255'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'django_packages_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'documentation_type': ('django.db.models.fields.CharField', [], {'default': "'sphinx'", 'max_length': '20'}),
|
||||
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
|
||||
'project_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'pub_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'related_projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['projects.Project']", 'null': 'True', 'through': "orm['projects.ProjectRelationship']", 'blank': 'True'}),
|
||||
'repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'repo_type': ('django.db.models.fields.CharField', [], {'default': "'git'", 'max_length': '10'}),
|
||||
'requirements_file': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'skip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'suffix': ('django.db.models.fields.CharField', [], {'default': "'.rst'", 'max_length': '10'}),
|
||||
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}),
|
||||
'use_system_packages': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'use_virtualenv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'version_privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'})
|
||||
},
|
||||
'projects.projectrelationship': {
|
||||
'Meta': {'object_name': 'ProjectRelationship'},
|
||||
'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'superprojects'", 'to': "orm['projects.Project']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subprojects'", 'to': "orm['projects.Project']"})
|
||||
},
|
||||
'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
|
||||
},
|
||||
'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
|
||||
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['projects']
|
|
@ -9,6 +9,8 @@ from django.db import models
|
|||
from django.template.defaultfilters import slugify
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from guardian.shortcuts import assign, get_objects_for_user
|
||||
|
||||
from projects import constants
|
||||
from projects.exceptions import ProjectImportError
|
||||
from projects.templatetags.projects_tags import sort_version_aware
|
||||
|
@ -24,10 +26,44 @@ from vcs_support.utils import Lock
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
class ProjectManager(models.Manager):
|
||||
def _filter_queryset(self, user, privacy_level):
|
||||
if isinstance(privacy_level, basestring):
|
||||
privacy_level = (privacy_level,)
|
||||
queryset = Project.objects.filter(privacy_level__in=privacy_level)
|
||||
if not user:
|
||||
return queryset
|
||||
if user.is_authenticated():
|
||||
# Add in possible user-specific views
|
||||
user_queryset = get_objects_for_user(user, 'projects.view_project')
|
||||
queryset = user_queryset | queryset
|
||||
return queryset.filter(skip=False)
|
||||
|
||||
def live(self, *args, **kwargs):
|
||||
base_qs = self.filter(skip=False)
|
||||
return base_qs.filter(*args, **kwargs)
|
||||
|
||||
def public(self, user=None, *args, **kwargs):
|
||||
"""
|
||||
Query for projects, privacy_level == public, and skip = False
|
||||
"""
|
||||
queryset = self._filter_queryset(user, privacy_level=constants.PUBLIC)
|
||||
return queryset.filter(*args, **kwargs)
|
||||
|
||||
def protected(self, user=None, *args, **kwargs):
|
||||
"""
|
||||
Query for projects, privacy_level != private, and skip = False
|
||||
"""
|
||||
queryset = self._filter_queryset(user, privacy_level=(constants.PUBLIC, constants.PROTECTED))
|
||||
return queryset.filter(*args, **kwargs)
|
||||
|
||||
def private(self, user=None, *args, **kwargs):
|
||||
"""
|
||||
Query for projects, privacy_level != private, and skip = False
|
||||
"""
|
||||
queryset = self._filter_queryset(user, privacy_level=constants.PRIVATE)
|
||||
return queryset.filter(*args, **kwargs)
|
||||
|
||||
|
||||
class ProjectRelationship(models.Model):
|
||||
parent = models.ForeignKey('Project', verbose_name=_('Parent'), related_name='subprojects')
|
||||
child = models.ForeignKey('Project', verbose_name=_('Child'), related_name='superprojects')
|
||||
|
@ -86,6 +122,12 @@ class Project(models.Model):
|
|||
help_text=_("Give the virtual environment access to the global sites-packages dir"))
|
||||
django_packages_url = models.CharField(_('Django Packages URL'), max_length=255, blank=True)
|
||||
crate_url = models.CharField(_('Crate URL'), max_length=255, blank=True)
|
||||
privacy_level = models.CharField(_('Privacy Level'), max_length=20,
|
||||
choices=constants.PRIVACY_CHOICES, default='public',
|
||||
help_text=_("Level of privacy that you want on the repository. Protected means public but not in listings."))
|
||||
version_privacy_level = models.CharField(_('Version Privacy Level'), max_length=20,
|
||||
choices=constants.PRIVACY_CHOICES, default='public',
|
||||
help_text=_("Default level of privacy you want on built versions of documentation."))
|
||||
|
||||
#Subprojects
|
||||
related_projects = models.ManyToManyField('self', verbose_name=_('Related projects'), blank=True, null=True, symmetrical=False, through=ProjectRelationship)
|
||||
|
@ -95,6 +137,10 @@ class Project(models.Model):
|
|||
|
||||
class Meta:
|
||||
ordering = ('slug',)
|
||||
permissions = (
|
||||
# Translators: Permission around whether a user can view the project
|
||||
('view_project', _('View Project')),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
@ -117,7 +163,10 @@ class Project(models.Model):
|
|||
self.slug = slugify(self.name)
|
||||
if self.slug == '':
|
||||
raise Exception(_("Model must have slug"))
|
||||
super(Project, self).save(*args, **kwargs)
|
||||
obj = super(Project, self).save(*args, **kwargs)
|
||||
for owner in self.users.all():
|
||||
assign('view_project', owner, self)
|
||||
return obj
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('projects_detail', args=[self.slug])
|
||||
|
|
|
@ -10,6 +10,8 @@ from django.shortcuts import get_object_or_404, render_to_response
|
|||
from django.template import RequestContext
|
||||
from django.views.generic.list_detail import object_list
|
||||
|
||||
from guardian.shortcuts import assign
|
||||
|
||||
from builds.forms import AliasForm
|
||||
from projects.forms import (ImportProjectForm, build_versions_form,
|
||||
build_upload_html_form, SubprojectForm)
|
||||
|
@ -123,6 +125,7 @@ def project_import(request):
|
|||
if request.method == 'POST' and form.is_valid():
|
||||
project = form.save()
|
||||
form.instance.users.add(request.user)
|
||||
assign('view_project', request.user, project)
|
||||
project_manage = reverse('projects_detail', args=[project.slug])
|
||||
return HttpResponseRedirect(project_manage + '?docs_not_built=True')
|
||||
|
||||
|
|
|
@ -9,19 +9,21 @@ from django.template import RequestContext
|
|||
from django.views.generic.list_detail import object_list
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from guardian.decorators import permission_required_or_403
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from taggit.models import Tag
|
||||
|
||||
from core.views import serve_docs
|
||||
from projects.models import Project
|
||||
from projects.utils import highest_version
|
||||
|
||||
from taggit.models import Tag
|
||||
|
||||
def project_index(request, username=None, tag=None):
|
||||
"""
|
||||
The list of projects, which will optionally filter by user or tag,
|
||||
in which case a 'person' or 'tag' will be added to the context
|
||||
"""
|
||||
queryset = Project.objects.live()
|
||||
queryset = Project.objects.public(request.user)
|
||||
if username:
|
||||
user = get_object_or_404(User, username=username)
|
||||
queryset = queryset.filter(user=user)
|
||||
|
@ -42,15 +44,19 @@ def project_index(request, username=None, tag=None):
|
|||
template_object_name='project',
|
||||
)
|
||||
|
||||
|
||||
def project_detail(request, project_slug):
|
||||
"""
|
||||
A detail view for a project with various dataz
|
||||
"""
|
||||
project = get_object_or_404(Project, slug=project_slug)
|
||||
queryset = Project.objects.protected(request.user)
|
||||
project = get_object_or_404(queryset, slug=project_slug)
|
||||
versions = project.versions.public(request.user, project)
|
||||
return render_to_response(
|
||||
'projects/project_detail.html',
|
||||
{
|
||||
'project': project,
|
||||
'versions': versions,
|
||||
},
|
||||
context_instance=RequestContext(request),
|
||||
)
|
||||
|
@ -59,7 +65,7 @@ def project_downloads(request, project_slug):
|
|||
"""
|
||||
A detail view for a project with various dataz
|
||||
"""
|
||||
project = get_object_or_404(Project, slug=project_slug)
|
||||
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
|
||||
versions = project.ordered_active_versions()
|
||||
version_data = SortedDict()
|
||||
for version in versions:
|
||||
|
|
|
@ -5,3 +5,4 @@ from test_backend import *
|
|||
from test_celery import *
|
||||
from test_hacks import *
|
||||
from test_post_commit_hooks import *
|
||||
from test_privacy import *
|
||||
|
|
|
@ -18,14 +18,14 @@ class APIBuildTests(TestCase):
|
|||
resp = self.client.post('/api/v1/build/',
|
||||
data=json.dumps(post_data),
|
||||
content_type='application/json',
|
||||
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('eric:test')
|
||||
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('super:test')
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
self.assertEqual(resp['location'],
|
||||
'http://testserver/api/v1/build/1/')
|
||||
resp = self.client.get('/api/v1/build/1/',
|
||||
data={'format': 'json'},
|
||||
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('eric:test')
|
||||
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('super:test')
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
obj = json.loads(resp.content)
|
||||
|
@ -46,7 +46,7 @@ class APITests(TestCase):
|
|||
resp = self.client.post('/api/v1/project/',
|
||||
data=json.dumps(post_data),
|
||||
content_type='application/json',
|
||||
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('eric:test')
|
||||
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('super:test')
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
self.assertEqual(resp['location'],
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
import logging
|
||||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from builds.models import Version
|
||||
from projects.models import Project
|
||||
from projects import tasks
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrivacyTests(TestCase):
|
||||
fixtures = ["eric"]
|
||||
|
||||
def tearDown(self):
|
||||
tasks.update_docs = self.old_bd
|
||||
|
||||
def setUp(self):
|
||||
self.old_bd = tasks.update_docs
|
||||
def mock(*args, **kwargs):
|
||||
pass
|
||||
#log.info("Mocking for great profit and speed.")
|
||||
tasks.update_docs.delay = mock
|
||||
|
||||
def _create_kong(self, privacy_level='private', version_privacy_level='private'):
|
||||
self.client.login(username='eric', password='test')
|
||||
log.info("Making kong with privacy: %s and version privacy: %s" % (privacy_level, version_privacy_level))
|
||||
r = self.client.post(
|
||||
'/dashboard/import/',
|
||||
{'repo_type': 'git', 'name': 'Django Kong',
|
||||
'tags': 'big, fucking, monkey', 'default_branch': '',
|
||||
'project_url': 'http://django-kong.rtfd.org',
|
||||
'repo': 'https://github.com/ericholscher/django-kong',
|
||||
'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be',
|
||||
'default_version': 'latest',
|
||||
'privacy_level': privacy_level,
|
||||
'version_privacy_level': version_privacy_level,
|
||||
'description': 'OOHHH AH AH AH KONG SMASH',
|
||||
'documentation_type': 'sphinx'})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertAlmostEqual(Project.objects.count(), 1)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return Project.objects.get(slug='django-kong')
|
||||
|
||||
|
||||
def test_private_repo(self):
|
||||
"""
|
||||
Check that private projects don't show up in: builds, downloads, detail, homepage
|
||||
"""
|
||||
kong = self._create_kong('private', 'private')
|
||||
|
||||
self.client.login(username='eric', password='test')
|
||||
r = self.client.get('/')
|
||||
self.assertTrue('Django Kong' in r.content)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/builds/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/projects/django-kong/downloads/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.client.login(username='tester', password='test')
|
||||
r = self.client.get('/')
|
||||
self.assertTrue('Django Kong' not in r.content)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 404)
|
||||
r = self.client.get('/builds/django-kong/')
|
||||
self.assertEqual(r.status_code, 404)
|
||||
r = self.client.get('/projects/django-kong/downloads/')
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
|
||||
def test_protected_repo(self):
|
||||
"""
|
||||
Check that protected projects don't show up in: builds, downloads, detail, project list
|
||||
"""
|
||||
kong = self._create_kong('protected', 'protected')
|
||||
|
||||
self.client.login(username='eric', password='test')
|
||||
r = self.client.get('/')
|
||||
self.assertTrue('Django Kong' in r.content)
|
||||
r = self.client.get('/projects/')
|
||||
self.assertTrue('Django Kong' in r.content)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/builds/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/projects/django-kong/downloads/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.client.login(username='tester', password='test')
|
||||
r = self.client.get('/')
|
||||
self.assertTrue('Django Kong' not in r.content)
|
||||
r = self.client.get('/projects/')
|
||||
self.assertTrue('Django Kong' not in r.content)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/builds/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/projects/django-kong/downloads/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_public_repo(self):
|
||||
"""
|
||||
Check that public projects show up in: builds, downloads, detail, homepage
|
||||
"""
|
||||
kong = self._create_kong('public', 'public')
|
||||
|
||||
self.client.login(username='eric', password='test')
|
||||
r = self.client.get('/')
|
||||
self.assertTrue('Django Kong' in r.content)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/builds/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/projects/django-kong/downloads/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.client.login(username='tester', password='test')
|
||||
r = self.client.get('/')
|
||||
self.assertTrue('Django Kong' in r.content)
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/builds/django-kong/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = self.client.get('/projects/django-kong/downloads/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_private_branch(self):
|
||||
kong = self._create_kong('public', 'private')
|
||||
|
||||
self.client.login(username='eric', password='test')
|
||||
kong_1 = Version.objects.create(project=kong, identifier='test id', verbose_name='test verbose', slug='test-slug')
|
||||
r = self.client.post('/dashboard/django-kong/versions/', {'version-test-slug': 'on', 'privacy-test-slug': 'private' })
|
||||
self.assertEqual(Version.objects.count(), 1)
|
||||
self.assertEqual(Version.objects.all()[0].privacy_level, 'private')
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertTrue('test-slug' in r.content)
|
||||
|
||||
# Make sure it doesn't show up as tester
|
||||
self.client.login(username='tester', password='test')
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertTrue('test-slug' not in r.content)
|
||||
|
||||
def test_protected_branch(self):
|
||||
kong = self._create_kong('public', 'protected')
|
||||
|
||||
self.client.login(username='eric', password='test')
|
||||
kong_1 = Version.objects.create(project=kong, identifier='test id', verbose_name='test verbose', slug='test-slug')
|
||||
r = self.client.post('/dashboard/django-kong/versions/', {'version-test-slug': 'on', 'privacy-test-slug': 'protected'})
|
||||
self.assertEqual(Version.objects.count(), 1)
|
||||
self.assertEqual(Version.objects.all()[0].privacy_level, 'protected')
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertTrue('test-slug' in r.content)
|
||||
|
||||
# Make sure it doesn't show up as tester
|
||||
self.client.login(username='tester', password='test')
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertTrue('test-slug' not in r.content)
|
||||
|
||||
def test_public_branch(self):
|
||||
kong = self._create_kong('public', 'public')
|
||||
|
||||
self.client.login(username='eric', password='test')
|
||||
kong_1 = Version.objects.create(project=kong, identifier='test id', verbose_name='test verbose', slug='test-slug')
|
||||
r = self.client.post('/dashboard/django-kong/versions/', {'version-test-slug': 'on', 'privacy-test-slug': 'public'})
|
||||
self.assertEqual(Version.objects.count(), 1)
|
||||
self.assertEqual(Version.objects.all()[0].privacy_level, 'public')
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertTrue('test-slug' in r.content)
|
||||
|
||||
# Make sure it doesn't show up as tester
|
||||
self.client.login(username='tester', password='test')
|
||||
r = self.client.get('/projects/django-kong/')
|
||||
self.assertTrue('test-slug' in r.content)
|
||||
|
||||
|
||||
def test_public_repo_api(self):
|
||||
kong = self._create_kong('public', 'public')
|
||||
self.client.login(username='eric', password='test')
|
||||
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = self.client.get("http://testserver/api/v1/project/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['meta']['total_count'], 1)
|
||||
|
||||
self.client.login(username='tester', password='test')
|
||||
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = self.client.get("http://testserver/api/v1/project/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['meta']['total_count'], 1)
|
||||
|
||||
|
||||
def test_protected_repo_api(self):
|
||||
kong = self._create_kong('protected', 'protected')
|
||||
self.client.login(username='eric', password='test')
|
||||
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = self.client.get("http://testserver/api/v1/project/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['meta']['total_count'], 1)
|
||||
|
||||
|
||||
self.client.login(username='tester', password='test')
|
||||
resp = self.client.get("http://testserver/api/v1/project/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['meta']['total_count'], 0)
|
||||
|
||||
# Need to figure out how to properly filter the detail view in tastypie.
|
||||
# Protected stuff won't show up in detail pages on the API currently.
|
||||
"""
|
||||
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
"""
|
||||
|
||||
|
||||
def test_private_repo_api(self):
|
||||
kong = self._create_kong('private', 'private')
|
||||
self.client.login(username='eric', password='test')
|
||||
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = self.client.get("http://testserver/api/v1/project/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['meta']['total_count'], 1)
|
||||
|
||||
self.client.login(username='tester', password='test')
|
||||
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
resp = self.client.get("http://testserver/api/v1/project/",
|
||||
data={"format": "json"}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['meta']['total_count'], 0)
|
|
@ -19,6 +19,8 @@ class Testmaker(TestCase):
|
|||
'repo': 'https://github.com/ericholscher/django-kong',
|
||||
'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be',
|
||||
'default_version': 'latest',
|
||||
'privacy_level': 'public',
|
||||
'version_privacy_level': 'public',
|
||||
'description': 'OOHHH AH AH AH KONG SMASH',
|
||||
'documentation_type': 'sphinx'})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
|
|
@ -27,6 +27,11 @@ LOGS_ROOT = os.path.join(SITE_ROOT, 'logs')
|
|||
MEDIA_ROOT = '%s/media/' % (SITE_ROOT)
|
||||
MEDIA_URL = '/media/'
|
||||
ADMIN_MEDIA_PREFIX = '/media/admin/'
|
||||
# For 1.4
|
||||
STATIC_ROOT = ''
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = ()
|
||||
STATICFILES_FINDERS = ()
|
||||
|
||||
CACHE_BACKEND = 'memcached://localhost:11211/'
|
||||
CACHE_KEY_PREFIX = 'docs'
|
||||
|
@ -97,11 +102,11 @@ INSTALLED_APPS = [
|
|||
'registration',
|
||||
'profiles',
|
||||
'taggit',
|
||||
'south',
|
||||
#'south',
|
||||
'basic.flagging',
|
||||
'djcelery',
|
||||
'djangosecure',
|
||||
#'celery_haystack',
|
||||
'guardian',
|
||||
|
||||
#daniellindsleyrocksdahouse
|
||||
'haystack',
|
||||
|
@ -149,6 +154,10 @@ backup_count = 1000
|
|||
if DEBUG:
|
||||
backup_count = 2
|
||||
|
||||
# Guardian Settings
|
||||
GUARDIAN_RAISE_403 = True
|
||||
ANONYMOUS_USER_ID = -1
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
|
|
|
@ -25,16 +25,26 @@
|
|||
<br>
|
||||
<!-- BEGIN search bar -->
|
||||
|
||||
<h3>{% trans "Privacy Level" %}</h3>
|
||||
<div id="privacy_level">
|
||||
<p>
|
||||
{{ project.get_privacy_level_display }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if project.active_versions.count %}
|
||||
|
||||
{% if versions %}
|
||||
<h3> {% trans "Versions" %} </h3>
|
||||
|
||||
<p>
|
||||
{% for version in project.ordered_active_versions %}
|
||||
{% for version in versions %}
|
||||
<div style="float: left;" class="version_left">
|
||||
{% if version.uploaded or version.built %}
|
||||
{# Link to the docs #}
|
||||
<a href="{{ version.get_absolute_url }}">{{ version.slug }}</a>
|
||||
{% if request.user in project.users.all %}
|
||||
({{ version.get_privacy_level_display }})
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans with version.slug as slug %}{{ slug }} (Build Failed){% endblocktrans %}
|
||||
{% endif %}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% if built %}
|
||||
<a href="{{ url }}">{% trans "(Built)" %}</a>
|
||||
{#<img src="{{ MEDIA_URL }}images/icon-built.png" alt="{% trans "built" %}" />#}
|
||||
<a href="{{ url }}">({% trans "Built" %})</a>
|
||||
{% else %}
|
||||
{% if uploaded %}
|
||||
<a href="{{ url }}">{% trans "(Uploaded)" %}</a>
|
||||
{#<img src="{{ MEDIA_URL }}images/icon-not-built.png" alt="{% trans "not built" %}" />#}
|
||||
<a href="{{ url }}">({% trans "Uploaded" %})</a>
|
||||
{% else %}
|
||||
{% trans "(Not built)" %}
|
||||
{#<img src="{{ MEDIA_URL }}images/icon-not-built.png" alt="{% trans "not built" %}" />#}
|
||||
|
||||
({% trans "Not built" %})
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -16,7 +16,23 @@
|
|||
{% trans "Choose which versions you would like to publish besides the latest revision." %}
|
||||
</p>
|
||||
<form method="post" action=".">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
{% for field in form %}
|
||||
{% if forloop.first %}
|
||||
{# For choosing the default version #}
|
||||
<h5> {{ field.label }} </h5>
|
||||
{{ field }}
|
||||
{% else %}
|
||||
{% if field.label != "privacy" %}
|
||||
<h3> {{ field.label}} </h3>
|
||||
{{ field }}
|
||||
{% else %}
|
||||
{{ field }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<br>
|
||||
{% endfor %}
|
||||
|
||||
<p>
|
||||
<input style="display: inline;" type="submit" value="{% trans "Submit" %}">
|
||||
</p>
|
||||
|
|
Loading…
Reference in New Issue