diff options
author | Bo Shi <bs1984@gmail.com> | 2009-12-22 23:27:32 -0500 |
---|---|---|
committer | Bo Shi <bs1984@gmail.com> | 2009-12-22 23:27:32 -0500 |
commit | eb834c564045a07e0014c5754231a857f13db663 (patch) | |
tree | e8d39787fb7b59a47620d13554658e936cd45308 | |
parent | 93d46f74d7dec332670ed8671708d55d5134288a (diff) | |
parent | 1e2fdc99abff24259e1c47651ca34373a2dcf9d0 (diff) | |
download | chishop-eb834c564045a07e0014c5754231a857f13db663.tar.bz2 chishop-eb834c564045a07e0014c5754231a857f13db663.tar.xz chishop-eb834c564045a07e0014c5754231a857f13db663.zip |
Merge branch 'master' of git://github.com/ask/chishop
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | README | 7 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | buildout.cfg | 2 | ||||
-rw-r--r-- | chishop/conf/__init__.py | 0 | ||||
-rw-r--r-- | chishop/conf/default.py | 111 | ||||
-rw-r--r-- | chishop/development.py | 23 | ||||
-rw-r--r-- | chishop/media/style/djangopypi.css | 4 | ||||
-rw-r--r-- | chishop/production_example.py | 18 | ||||
-rw-r--r-- | chishop/settings.py | 112 | ||||
-rw-r--r-- | chishop/templates/base.html | 5 | ||||
-rw-r--r-- | chishop/templates/djangopypi/search.html | 4 | ||||
-rw-r--r-- | chishop/templates/djangopypi/search_results.html | 31 | ||||
-rw-r--r-- | chishop/urls.py | 2 | ||||
-rw-r--r-- | djangopypi/forms.py | 3 | ||||
-rw-r--r-- | djangopypi/http.py | 51 | ||||
-rw-r--r-- | djangopypi/tests.py | 22 | ||||
-rw-r--r-- | djangopypi/urls.py | 5 | ||||
-rw-r--r-- | djangopypi/views.py | 229 | ||||
-rw-r--r-- | djangopypi/views/__init__.py | 76 | ||||
-rw-r--r-- | djangopypi/views/dists.py | 79 | ||||
-rw-r--r-- | djangopypi/views/search.py | 24 | ||||
-rw-r--r-- | djangopypi/views/users.py | 24 |
24 files changed, 477 insertions, 362 deletions
@@ -5,6 +5,7 @@ | |||
5 | *.sqlite-journal | 5 | *.sqlite-journal |
6 | settings_local.py | 6 | settings_local.py |
7 | .*.sw[po] | 7 | .*.sw[po] |
8 | *.kpf | ||
8 | dist/ | 9 | dist/ |
9 | *.egg-info | 10 | *.egg-info |
10 | doc/__build/* | 11 | doc/__build/* |
@@ -14,4 +15,4 @@ parts | |||
14 | eggs | 15 | eggs |
15 | bin | 16 | bin |
16 | developer-eggs | 17 | developer-eggs |
17 | downloads \ No newline at end of file | 18 | downloads |
@@ -4,3 +4,6 @@ Russell Sim <russell.sim@gmail.com> | |||
4 | Brian Rosner <brosner@gmail.com> | 4 | Brian Rosner <brosner@gmail.com> |
5 | Hugo Lopes Tavares <hltbra@gmail.com> | 5 | Hugo Lopes Tavares <hltbra@gmail.com> |
6 | Sverre Johansen <sverre.johansen@gmail.com> | 6 | Sverre Johansen <sverre.johansen@gmail.com> |
7 | Bo Shi <bs@alum.mit.edu> | ||
8 | Carl Meyer <carl@dirtcircle.com> | ||
9 | VinÃcius das Chagas Silva <vinimaster@gmail.com> | ||
@@ -27,6 +27,13 @@ Run the PyPI server | |||
27 | Please note that ``chishop/media/dists`` has to be writable by the | 27 | Please note that ``chishop/media/dists`` has to be writable by the |
28 | user the web-server is running as. | 28 | user the web-server is running as. |
29 | 29 | ||
30 | In production | ||
31 | ------------- | ||
32 | |||
33 | You may want to copy the file ``chishop/production_example.py`` and modify | ||
34 | for use as your production settings; you will also need to modify | ||
35 | ``bin/django.wsgi`` to refer to your production settings. | ||
36 | |||
30 | Using Setuptools | 37 | Using Setuptools |
31 | ================ | 38 | ================ |
32 | 39 | ||
@@ -11,7 +11,6 @@ PyPI feature replication | |||
11 | I'm not sure what the difference between a co-owner and maintainer is, | 11 | I'm not sure what the difference between a co-owner and maintainer is, |
12 | maybe it's just a label. | 12 | maybe it's just a label. |
13 | * Package author admin interface (submit, edit, view) | 13 | * Package author admin interface (submit, edit, view) |
14 | * Search | ||
15 | * Documentation upload | 14 | * Documentation upload |
16 | * Ratings | 15 | * Ratings |
17 | * Random Monty Python quotes :-) | 16 | * Random Monty Python quotes :-) |
diff --git a/buildout.cfg b/buildout.cfg index 04e0acf..160db4c 100644 --- a/buildout.cfg +++ b/buildout.cfg | |||
@@ -8,7 +8,7 @@ eggs = pkginfo | |||
8 | [django] | 8 | [django] |
9 | recipe = djangorecipe | 9 | recipe = djangorecipe |
10 | version = 1.1.1 | 10 | version = 1.1.1 |
11 | settings = development | 11 | settings = settings |
12 | eggs = ${buildout:eggs} | 12 | eggs = ${buildout:eggs} |
13 | test = djangopypi | 13 | test = djangopypi |
14 | project = chishop | 14 | project = chishop |
diff --git a/chishop/conf/__init__.py b/chishop/conf/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/chishop/conf/__init__.py | |||
diff --git a/chishop/conf/default.py b/chishop/conf/default.py new file mode 100644 index 0000000..97002b6 --- /dev/null +++ b/chishop/conf/default.py | |||
@@ -0,0 +1,111 @@ | |||
1 | # Django settings for djangopypi project. | ||
2 | import os | ||
3 | |||
4 | ADMINS = ( | ||
5 | # ('Your Name', 'your_email@domain.com'), | ||
6 | ) | ||
7 | |||
8 | # Allow uploading a new distribution file for a project version | ||
9 | # if a file of that type already exists. | ||
10 | # | ||
11 | # The default on PyPI is to not allow this, but it can be real handy | ||
12 | # if you're sloppy. | ||
13 | DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False | ||
14 | DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' | ||
15 | |||
16 | # change to False if you do not want Django's default server to serve static pages | ||
17 | LOCAL_DEVELOPMENT = True | ||
18 | |||
19 | REGISTRATION_OPEN = True | ||
20 | ACCOUNT_ACTIVATION_DAYS = 7 | ||
21 | LOGIN_REDIRECT_URL = "/" | ||
22 | |||
23 | EMAIL_HOST = '' | ||
24 | DEFAULT_FROM_EMAIL = '' | ||
25 | SERVER_EMAIL = DEFAULT_FROM_EMAIL | ||
26 | |||
27 | MANAGERS = ADMINS | ||
28 | |||
29 | DATABASE_ENGINE = '' | ||
30 | DATABASE_NAME = '' | ||
31 | DATABASE_USER = '' | ||
32 | DATABASE_PASSWORD = '' | ||
33 | DATABASE_HOST = '' | ||
34 | DATABASE_PORT = '' | ||
35 | |||
36 | # Local time zone for this installation. Choices can be found here: | ||
37 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name | ||
38 | # although not all choices may be available on all operating systems. | ||
39 | # If running in a Windows environment this must be set to the same as your | ||
40 | # system time zone. | ||
41 | TIME_ZONE = 'America/Chicago' | ||
42 | |||
43 | # Language code for this installation. All choices can be found here: | ||
44 | # http://www.i18nguy.com/unicode/language-identifiers.html | ||
45 | LANGUAGE_CODE = 'en-us' | ||
46 | |||
47 | SITE_ID = 1 | ||
48 | |||
49 | # If you set this to False, Django will make some optimizations so as not | ||
50 | # to load the internationalization machinery. | ||
51 | USE_I18N = True | ||
52 | |||
53 | # Absolute path to the directory that holds media. | ||
54 | # Example: "/home/media/media.lawrence.com/" | ||
55 | here = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) | ||
56 | MEDIA_ROOT = os.path.join(here, 'media') | ||
57 | |||
58 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a | ||
59 | # trailing slash if there is a path component (optional in other cases). | ||
60 | # Examples: "http://media.lawrence.com", "http://example.com/media/" | ||
61 | MEDIA_URL = '/media/' | ||
62 | |||
63 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a | ||
64 | # trailing slash. | ||
65 | # Examples: "http://foo.com/media/", "/media/". | ||
66 | ADMIN_MEDIA_PREFIX = '/admin-media/' | ||
67 | |||
68 | # Make this unique, and don't share it with anybody. | ||
69 | SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu' | ||
70 | |||
71 | # List of callables that know how to import templates from various sources. | ||
72 | TEMPLATE_LOADERS = ( | ||
73 | 'django.template.loaders.filesystem.load_template_source', | ||
74 | 'django.template.loaders.app_directories.load_template_source', | ||
75 | # 'django.template.loaders.eggs.load_template_source', | ||
76 | ) | ||
77 | |||
78 | MIDDLEWARE_CLASSES = ( | ||
79 | 'django.middleware.common.CommonMiddleware', | ||
80 | 'django.contrib.sessions.middleware.SessionMiddleware', | ||
81 | 'django.contrib.auth.middleware.AuthenticationMiddleware', | ||
82 | ) | ||
83 | |||
84 | ROOT_URLCONF = 'urls' | ||
85 | |||
86 | TEMPLATE_CONTEXT_PROCESSORS = ( | ||
87 | "django.core.context_processors.auth", | ||
88 | "django.core.context_processors.debug", | ||
89 | "django.core.context_processors.i18n", | ||
90 | "django.core.context_processors.media", | ||
91 | "django.core.context_processors.request", | ||
92 | ) | ||
93 | |||
94 | TEMPLATE_DIRS = ( | ||
95 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". | ||
96 | # Always use forward slashes, even on Windows. | ||
97 | # Don't forget to use absolute paths, not relative paths. | ||
98 | os.path.join(os.path.dirname(os.path.dirname(__file__)), "templates"), | ||
99 | ) | ||
100 | |||
101 | INSTALLED_APPS = ( | ||
102 | 'django.contrib.auth', | ||
103 | 'django.contrib.contenttypes', | ||
104 | 'django.contrib.sessions', | ||
105 | 'django.contrib.sites', | ||
106 | 'django.contrib.admin', | ||
107 | 'django.contrib.markup', | ||
108 | 'django.contrib.admindocs', | ||
109 | 'registration', | ||
110 | 'djangopypi', | ||
111 | ) | ||
diff --git a/chishop/development.py b/chishop/development.py deleted file mode 100644 index 0671fc2..0000000 --- a/chishop/development.py +++ /dev/null | |||
@@ -1,23 +0,0 @@ | |||
1 | from settings import * | ||
2 | import os | ||
3 | |||
4 | DEBUG = True | ||
5 | TEMPLATE_DEBUG = DEBUG | ||
6 | LOCAL_DEVELOPMENT = True | ||
7 | |||
8 | if LOCAL_DEVELOPMENT: | ||
9 | import sys | ||
10 | sys.path.append(os.path.dirname(__file__)) | ||
11 | |||
12 | ADMINS = ( | ||
13 | ('chishop', 'example@example.org'), | ||
14 | ) | ||
15 | |||
16 | MANAGERS = ADMINS | ||
17 | |||
18 | DATABASE_ENGINE = 'sqlite3' | ||
19 | DATABASE_NAME = os.path.join(here, 'devdatabase.db') | ||
20 | DATABASE_USER = '' | ||
21 | DATABASE_PASSWORD = '' | ||
22 | DATABASE_HOST = '' | ||
23 | DATABASE_PORT = '' | ||
diff --git a/chishop/media/style/djangopypi.css b/chishop/media/style/djangopypi.css new file mode 100644 index 0000000..e6fbfd9 --- /dev/null +++ b/chishop/media/style/djangopypi.css | |||
@@ -0,0 +1,4 @@ | |||
1 | .search { | ||
2 | text-align:right; | ||
3 | margin-right: 10px; | ||
4 | } \ No newline at end of file | ||
diff --git a/chishop/production_example.py b/chishop/production_example.py new file mode 100644 index 0000000..b64623e --- /dev/null +++ b/chishop/production_example.py | |||
@@ -0,0 +1,18 @@ | |||
1 | from conf.default import * | ||
2 | import os | ||
3 | |||
4 | DEBUG = False | ||
5 | TEMPLATE_DEBUG = DEBUG | ||
6 | |||
7 | ADMINS = ( | ||
8 | ('chishop', 'example@example.org'), | ||
9 | ) | ||
10 | |||
11 | MANAGERS = ADMINS | ||
12 | |||
13 | DATABASE_ENGINE = 'postgresql_psycopg2' | ||
14 | DATABASE_NAME = 'chishop' | ||
15 | DATABASE_USER = 'chishop' | ||
16 | DATABASE_PASSWORD = 'chishop' | ||
17 | DATABASE_HOST = '' | ||
18 | DATABASE_PORT = '' | ||
diff --git a/chishop/settings.py b/chishop/settings.py index ee286b5..e68a4e5 100644 --- a/chishop/settings.py +++ b/chishop/settings.py | |||
@@ -1,113 +1,23 @@ | |||
1 | # Django settings for djangopypi project. | 1 | from conf.default import * |
2 | import os | 2 | import os |
3 | 3 | ||
4 | ADMINS = ( | 4 | DEBUG = True |
5 | # ('Your Name', 'your_email@domain.com'), | 5 | TEMPLATE_DEBUG = DEBUG |
6 | ) | ||
7 | |||
8 | # Allow uploading a new distribution file for a project version | ||
9 | # if a file of that type already exists. | ||
10 | # | ||
11 | # The default on PyPI is to not allow this, but it can be real handy | ||
12 | # if you're sloppy. | ||
13 | DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False | ||
14 | DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' | ||
15 | |||
16 | # change to False if you do not want Django's default server to serve static pages | ||
17 | LOCAL_DEVELOPMENT = True | 6 | LOCAL_DEVELOPMENT = True |
18 | 7 | ||
19 | REGISTRATION_OPEN = True | 8 | if LOCAL_DEVELOPMENT: |
20 | ACCOUNT_ACTIVATION_DAYS = 7 | 9 | import sys |
21 | LOGIN_REDIRECT_URL = "/" | 10 | sys.path.append(os.path.dirname(__file__)) |
22 | 11 | ||
23 | EMAIL_HOST = '' | 12 | ADMINS = ( |
24 | DEFAULT_FROM_EMAIL = '' | 13 | ('chishop', 'example@example.org'), |
25 | SERVER_EMAIL = DEFAULT_FROM_EMAIL | 14 | ) |
26 | 15 | ||
27 | MANAGERS = ADMINS | 16 | MANAGERS = ADMINS |
28 | 17 | ||
29 | DATABASE_ENGINE = '' | 18 | DATABASE_ENGINE = 'sqlite3' |
30 | DATABASE_NAME = '' | 19 | DATABASE_NAME = os.path.join(here, 'devdatabase.db') |
31 | DATABASE_USER = '' | 20 | DATABASE_USER = '' |
32 | DATABASE_PASSWORD = '' | 21 | DATABASE_PASSWORD = '' |
33 | DATABASE_HOST = '' | 22 | DATABASE_HOST = '' |
34 | DATABASE_PORT = '' | 23 | DATABASE_PORT = '' |
35 | |||
36 | # Local time zone for this installation. Choices can be found here: | ||
37 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name | ||
38 | # although not all choices may be available on all operating systems. | ||
39 | # If running in a Windows environment this must be set to the same as your | ||
40 | # system time zone. | ||
41 | TIME_ZONE = 'America/Chicago' | ||
42 | |||
43 | # Language code for this installation. All choices can be found here: | ||
44 | # http://www.i18nguy.com/unicode/language-identifiers.html | ||
45 | LANGUAGE_CODE = 'en-us' | ||
46 | |||
47 | SITE_ID = 1 | ||
48 | |||
49 | # If you set this to False, Django will make some optimizations so as not | ||
50 | # to load the internationalization machinery. | ||
51 | USE_I18N = True | ||
52 | |||
53 | # Absolute path to the directory that holds media. | ||
54 | # Example: "/home/media/media.lawrence.com/" | ||
55 | here = os.path.abspath(os.path.dirname(__file__)) | ||
56 | MEDIA_ROOT = os.path.join(here, 'media') | ||
57 | |||
58 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a | ||
59 | # trailing slash if there is a path component (optional in other cases). | ||
60 | # Examples: "http://media.lawrence.com", "http://example.com/media/" | ||
61 | MEDIA_URL = 'media/' | ||
62 | |||
63 | MEDIA_PREFIX = "/media/" | ||
64 | |||
65 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a | ||
66 | # trailing slash. | ||
67 | # Examples: "http://foo.com/media/", "/media/". | ||
68 | ADMIN_MEDIA_PREFIX = '/admin-media/' | ||
69 | |||
70 | # Make this unique, and don't share it with anybody. | ||
71 | SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu' | ||
72 | |||
73 | # List of callables that know how to import templates from various sources. | ||
74 | TEMPLATE_LOADERS = ( | ||
75 | 'django.template.loaders.filesystem.load_template_source', | ||
76 | 'django.template.loaders.app_directories.load_template_source', | ||
77 | # 'django.template.loaders.eggs.load_template_source', | ||
78 | ) | ||
79 | |||
80 | MIDDLEWARE_CLASSES = ( | ||
81 | 'django.middleware.common.CommonMiddleware', | ||
82 | 'django.contrib.sessions.middleware.SessionMiddleware', | ||
83 | 'django.contrib.auth.middleware.AuthenticationMiddleware', | ||
84 | ) | ||
85 | |||
86 | ROOT_URLCONF = 'urls' | ||
87 | |||
88 | TEMPLATE_CONTEXT_PROCESSORS = ( | ||
89 | "django.core.context_processors.auth", | ||
90 | "django.core.context_processors.debug", | ||
91 | "django.core.context_processors.i18n", | ||
92 | "django.core.context_processors.media", | ||
93 | "django.core.context_processors.request", | ||
94 | ) | ||
95 | |||
96 | TEMPLATE_DIRS = ( | ||
97 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". | ||
98 | # Always use forward slashes, even on Windows. | ||
99 | # Don't forget to use absolute paths, not relative paths. | ||
100 | os.path.join(os.path.dirname(__file__), "templates"), | ||
101 | ) | ||
102 | |||
103 | INSTALLED_APPS = ( | ||
104 | 'django.contrib.auth', | ||
105 | 'django.contrib.contenttypes', | ||
106 | 'django.contrib.sessions', | ||
107 | 'django.contrib.sites', | ||
108 | 'django.contrib.admin', | ||
109 | 'django.contrib.markup', | ||
110 | 'django.contrib.admindocs', | ||
111 | 'registration', | ||
112 | 'djangopypi', | ||
113 | ) | ||
diff --git a/chishop/templates/base.html b/chishop/templates/base.html index 76483ce..dd797e7 100644 --- a/chishop/templates/base.html +++ b/chishop/templates/base.html | |||
@@ -2,6 +2,7 @@ | |||
2 | <html xmlns="http://www.w3.org/1999/xhtml" lang="en-au" xml:lang="en-au"> | 2 | <html xmlns="http://www.w3.org/1999/xhtml" lang="en-au" xml:lang="en-au"> |
3 | <head> | 3 | <head> |
4 | <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}"/> | 4 | <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}"/> |
5 | <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}style/djangopypi.css"/> | ||
5 | {% block extrastyle %}{% endblock %} | 6 | {% block extrastyle %}{% endblock %} |
6 | <meta http-equiv="content-type" content="text/html; charset=UTF-8"/> | 7 | <meta http-equiv="content-type" content="text/html; charset=UTF-8"/> |
7 | <title>{% block title %}{% endblock %}</title> | 8 | <title>{% block title %}{% endblock %}</title> |
@@ -20,6 +21,10 @@ | |||
20 | {% block site_logo %}{% endblock %} | 21 | {% block site_logo %}{% endblock %} |
21 | <h1 id="site-name">{% block site_name_header %}{% endblock %}</h1> | 22 | <h1 id="site-name">{% block site_name_header %}{% endblock %}</h1> |
22 | </div> | 23 | </div> |
24 | |||
25 | <div class="search"> | ||
26 | {% include "djangopypi/search.html" %} | ||
27 | </div> | ||
23 | 28 | ||
24 | <div id="user-tools"> | 29 | <div id="user-tools"> |
25 | {% if user.is_authenticated %} | 30 | {% if user.is_authenticated %} |
diff --git a/chishop/templates/djangopypi/search.html b/chishop/templates/djangopypi/search.html new file mode 100644 index 0000000..8269508 --- /dev/null +++ b/chishop/templates/djangopypi/search.html | |||
@@ -0,0 +1,4 @@ | |||
1 | <form action='search' method='post'> | ||
2 | <input type="text" name="search_term" id="search_term"> | ||
3 | <input type='submit' value=' Search '/> | ||
4 | </form> \ No newline at end of file | ||
diff --git a/chishop/templates/djangopypi/search_results.html b/chishop/templates/djangopypi/search_results.html new file mode 100644 index 0000000..dccf61f --- /dev/null +++ b/chishop/templates/djangopypi/search_results.html | |||
@@ -0,0 +1,31 @@ | |||
1 | {% extends "base_site.html" %} | ||
2 | |||
3 | {% block bread_crumbs_1 %}›Search{% endblock %} | ||
4 | |||
5 | {% block content %} | ||
6 | {% ifnotequal search_term ''%} | ||
7 | <h1>Index of Packages Matching '{{ search_term }}'</h1> | ||
8 | {% else %} | ||
9 | <h1>You need to supply a search term.</h1> | ||
10 | {% endifnotequal %} | ||
11 | {% if dists %} | ||
12 | <table> | ||
13 | <thead> | ||
14 | <th>Updated</th> | ||
15 | <th>Package</th> | ||
16 | <th>Summary</th> | ||
17 | </thead> | ||
18 | <tbody> | ||
19 | {% for dist in dists %} | ||
20 | <tr> | ||
21 | <td>{{ dist.updated|date:"d/m/y" }} | ||
22 | <td><a href="{{ dist.get_pypi_absolute_url }}"/>{{ dist.name }}</a></td> | ||
23 | <td>{{ dist.summary|truncatewords:10 }}</td> | ||
24 | </tr> | ||
25 | {% endfor %} | ||
26 | </tbody> | ||
27 | </table> | ||
28 | {% else %} | ||
29 | There were no matches. | ||
30 | {% endif %} | ||
31 | {% endblock content %} \ No newline at end of file | ||
diff --git a/chishop/urls.py b/chishop/urls.py index ab2e8a9..5a5dd77 100644 --- a/chishop/urls.py +++ b/chishop/urls.py | |||
@@ -10,7 +10,7 @@ urlpatterns = patterns('') | |||
10 | # Serve static pages. | 10 | # Serve static pages. |
11 | if settings.LOCAL_DEVELOPMENT: | 11 | if settings.LOCAL_DEVELOPMENT: |
12 | urlpatterns += patterns("django.views", | 12 | urlpatterns += patterns("django.views", |
13 | url(r"%s(?P<path>.*)$" % settings.MEDIA_URL[1:], "static.serve", { | 13 | url(r"^%s(?P<path>.*)$" % settings.MEDIA_URL[1:], "static.serve", { |
14 | "document_root": settings.MEDIA_ROOT})) | 14 | "document_root": settings.MEDIA_ROOT})) |
15 | 15 | ||
16 | urlpatterns += patterns("", | 16 | urlpatterns += patterns("", |
diff --git a/djangopypi/forms.py b/djangopypi/forms.py index 5484747..6a65d37 100644 --- a/djangopypi/forms.py +++ b/djangopypi/forms.py | |||
@@ -14,5 +14,4 @@ class ProjectForm(forms.ModelForm): | |||
14 | class ReleaseForm(forms.ModelForm): | 14 | class ReleaseForm(forms.ModelForm): |
15 | class Meta: | 15 | class Meta: |
16 | model = Release | 16 | model = Release |
17 | exclude = ['project'] | 17 | exclude = ['project'] \ No newline at end of file |
18 | |||
diff --git a/djangopypi/http.py b/djangopypi/http.py index 01081f7..7be5959 100644 --- a/djangopypi/http.py +++ b/djangopypi/http.py | |||
@@ -1,4 +1,8 @@ | |||
1 | from django.http import HttpResponse | 1 | from django.http import HttpResponse |
2 | from django.core.files.uploadedfile import SimpleUploadedFile | ||
3 | from django.utils.datastructures import MultiValueDict | ||
4 | from django.contrib.auth import authenticate | ||
5 | |||
2 | 6 | ||
3 | class HttpResponseNotImplemented(HttpResponse): | 7 | class HttpResponseNotImplemented(HttpResponse): |
4 | status_code = 501 | 8 | status_code = 501 |
@@ -12,4 +16,51 @@ class HttpResponseUnauthorized(HttpResponse): | |||
12 | self['WWW-Authenticate'] = 'Basic realm="%s"' % realm | 16 | self['WWW-Authenticate'] = 'Basic realm="%s"' % realm |
13 | 17 | ||
14 | 18 | ||
19 | def parse_distutils_request(request): | ||
20 | raw_post_data = request.raw_post_data | ||
21 | sep = raw_post_data.splitlines()[1] | ||
22 | items = raw_post_data.split(sep) | ||
23 | post_data = {} | ||
24 | files = {} | ||
25 | for part in filter(lambda e: not e.isspace(), items): | ||
26 | item = part.splitlines() | ||
27 | if len(item) < 2: | ||
28 | continue | ||
29 | header = item[1].replace("Content-Disposition: form-data; ", "") | ||
30 | kvpairs = header.split(";") | ||
31 | headers = {} | ||
32 | for kvpair in kvpairs: | ||
33 | if not kvpair: | ||
34 | continue | ||
35 | key, value = kvpair.split("=") | ||
36 | headers[key] = value.strip('"') | ||
37 | if "name" not in headers: | ||
38 | continue | ||
39 | content = part[len("\n".join(item[0:2]))+2:len(part)-1] | ||
40 | if "filename" in headers: | ||
41 | file = SimpleUploadedFile(headers["filename"], content, | ||
42 | content_type="application/gzip") | ||
43 | files["distribution"] = [file] | ||
44 | elif headers["name"] in post_data: | ||
45 | post_data[headers["name"]].append(content) | ||
46 | else: | ||
47 | # Distutils sends UNKNOWN for empty fields (e.g platform) | ||
48 | # [russell.sim@gmail.com] | ||
49 | if content == 'UNKNOWN': | ||
50 | post_data[headers["name"]] = [None] | ||
51 | else: | ||
52 | post_data[headers["name"]] = [content] | ||
53 | |||
54 | return MultiValueDict(post_data), MultiValueDict(files) | ||
55 | |||
15 | 56 | ||
57 | def login_basic_auth(request): | ||
58 | authentication = request.META.get("HTTP_AUTHORIZATION") | ||
59 | if not authentication: | ||
60 | return | ||
61 | (authmeth, auth) = authentication.split(' ', 1) | ||
62 | if authmeth.lower() != "basic": | ||
63 | return | ||
64 | auth = auth.strip().decode("base64") | ||
65 | username, password = auth.split(":", 1) | ||
66 | return authenticate(username=username, password=password) | ||
diff --git a/djangopypi/tests.py b/djangopypi/tests.py index 2d8c305..44ec3ac 100644 --- a/djangopypi/tests.py +++ b/djangopypi/tests.py | |||
@@ -1,6 +1,10 @@ | |||
1 | import unittest | 1 | import unittest |
2 | import StringIO | 2 | import StringIO |
3 | from djangopypi.views import parse_distutils_request | 3 | from djangopypi.views import parse_distutils_request |
4 | from djangopypi.models import Project, Classifier | ||
5 | from django.test.client import Client | ||
6 | from django.core.urlresolvers import reverse | ||
7 | from django.contrib.auth.models import User | ||
4 | 8 | ||
5 | def create_post_data(action): | 9 | def create_post_data(action): |
6 | data = { | 10 | data = { |
@@ -80,11 +84,27 @@ class TestParseWeirdPostData(unittest.TestCase): | |||
80 | data = create_post_data("submit") | 84 | data = create_post_data("submit") |
81 | raw_post_data = create_request(data) | 85 | raw_post_data = create_request(data) |
82 | post, files = parse_distutils_request(MockRequest(raw_post_data)) | 86 | post, files = parse_distutils_request(MockRequest(raw_post_data)) |
83 | print("post: %s files: %s" % (post, files)) | ||
84 | self.assertTrue(post) | 87 | self.assertTrue(post) |
85 | 88 | ||
86 | for key in post.keys(): | 89 | for key in post.keys(): |
87 | if isinstance(data[key], list): | 90 | if isinstance(data[key], list): |
88 | self.assertEquals(data[key], post.getlist(key)) | 91 | self.assertEquals(data[key], post.getlist(key)) |
92 | elif data[key] == "UNKNOWN": | ||
93 | self.assertTrue(post[key] is None) | ||
89 | else: | 94 | else: |
90 | self.assertEquals(post[key], data[key]) | 95 | self.assertEquals(post[key], data[key]) |
96 | |||
97 | class TestSearch(unittest.TestCase): | ||
98 | |||
99 | def setUp(self): | ||
100 | data = create_post_data("submit") | ||
101 | dummy_user = User.objects.create(username='krill', password='12345', | ||
102 | email='krill@opera.com') | ||
103 | Project.objects.create(name=data['name'], license=data['license'], | ||
104 | summary=data["summary"], owner=dummy_user) | ||
105 | |||
106 | |||
107 | def testSearchForPackage(self): | ||
108 | client = Client() | ||
109 | response = client.post(reverse('djangopypi-search'), {'search_term': 'foo'}) | ||
110 | self.assertTrue("The quick brown fox jumps over the lazy dog." in response.content) | ||
diff --git a/djangopypi/urls.py b/djangopypi/urls.py index d6cccb5..79b16be 100644 --- a/djangopypi/urls.py +++ b/djangopypi/urls.py | |||
@@ -19,5 +19,6 @@ urlpatterns = patterns("djangopypi.views", | |||
19 | url(r'^(?P<dist_name>[\w\d_\.\-]+)/$', "show_links", | 19 | url(r'^(?P<dist_name>[\w\d_\.\-]+)/$', "show_links", |
20 | {'template_name': 'djangopypi/pypi_show_links.html'}, | 20 | {'template_name': 'djangopypi/pypi_show_links.html'}, |
21 | name="djangopypi-pypi_show_links"), | 21 | name="djangopypi-pypi_show_links"), |
22 | ) | 22 | |
23 | 23 | url(r'^search','search',name='djangopypi-search') | |
24 | ) \ No newline at end of file | ||
diff --git a/djangopypi/views.py b/djangopypi/views.py deleted file mode 100644 index 8817f66..0000000 --- a/djangopypi/views.py +++ /dev/null | |||
@@ -1,229 +0,0 @@ | |||
1 | import cgi | ||
2 | import os | ||
3 | |||
4 | try: | ||
5 | from cStringIO import StringIO | ||
6 | except ImportError: | ||
7 | from StringIO import StringIO | ||
8 | |||
9 | from django.conf import settings | ||
10 | from django.http import Http404, HttpResponse, HttpResponseBadRequest | ||
11 | from django.http import QueryDict, HttpResponseForbidden | ||
12 | from django.shortcuts import render_to_response | ||
13 | from django.template import RequestContext | ||
14 | from django.utils.datastructures import MultiValueDict | ||
15 | from django.utils.translation import ugettext_lazy as _ | ||
16 | from django.core.files.uploadedfile import SimpleUploadedFile | ||
17 | from django.contrib.auth import authenticate, login | ||
18 | |||
19 | from registration.backends import get_backend | ||
20 | from registration.forms import RegistrationForm | ||
21 | |||
22 | from djangopypi.models import Project, Classifier, Release, UPLOAD_TO | ||
23 | from djangopypi.forms import ProjectForm, ReleaseForm | ||
24 | from djangopypi.http import HttpResponseUnauthorized | ||
25 | from djangopypi.http import HttpResponseNotImplemented | ||
26 | from djangopypi.utils import decode_fs | ||
27 | |||
28 | |||
29 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ | ||
30 | + "problems with that you should create a new release.") | ||
31 | |||
32 | |||
33 | def parse_distutils_request(request): | ||
34 | raw_post_data = request.raw_post_data | ||
35 | sep = raw_post_data.splitlines()[1] | ||
36 | items = raw_post_data.split(sep) | ||
37 | post_data = {} | ||
38 | files = {} | ||
39 | for part in filter(lambda e: not e.isspace(), items): | ||
40 | item = part.splitlines() | ||
41 | if len(item) < 2: | ||
42 | continue | ||
43 | header = item[1].replace("Content-Disposition: form-data; ", "") | ||
44 | kvpairs = header.split(";") | ||
45 | headers = {} | ||
46 | for kvpair in kvpairs: | ||
47 | if not kvpair: | ||
48 | continue | ||
49 | key, value = kvpair.split("=") | ||
50 | headers[key] = value.strip('"') | ||
51 | if "name" not in headers: | ||
52 | continue | ||
53 | content = part[len("\n".join(item[0:2]))+2:len(part)-1] | ||
54 | if "filename" in headers: | ||
55 | file = SimpleUploadedFile(headers["filename"], content, | ||
56 | content_type="application/gzip") | ||
57 | files["distribution"] = [file] | ||
58 | elif headers["name"] in post_data: | ||
59 | post_data[headers["name"]].append(content) | ||
60 | else: | ||
61 | # Distutils sends UNKNOWN for empty fields (e.g platform) | ||
62 | # [russell.sim@gmail.com] | ||
63 | if content == 'UNKNOWN': | ||
64 | post_data[headers["name"]] = [None] | ||
65 | else: | ||
66 | post_data[headers["name"]] = [content] | ||
67 | |||
68 | return MultiValueDict(post_data), MultiValueDict(files) | ||
69 | |||
70 | |||
71 | |||
72 | def login_basic_auth(request): | ||
73 | authentication = request.META.get("HTTP_AUTHORIZATION") | ||
74 | if not authentication: | ||
75 | return | ||
76 | (authmeth, auth) = authentication.split(' ', 1) | ||
77 | if authmeth.lower() != "basic": | ||
78 | return | ||
79 | auth = auth.strip().decode("base64") | ||
80 | username, password = auth.split(":", 1) | ||
81 | return authenticate(username=username, password=password) | ||
82 | |||
83 | |||
84 | def submit_project_or_release(user, post_data, files): | ||
85 | """Registers/updates a project or release""" | ||
86 | try: | ||
87 | project = Project.objects.get(name=post_data['name']) | ||
88 | if project.owner != user: | ||
89 | return HttpResponseForbidden( | ||
90 | "That project is owned by someone else!") | ||
91 | except Project.DoesNotExist: | ||
92 | project = None | ||
93 | |||
94 | project_form = ProjectForm(post_data, instance=project) | ||
95 | if project_form.is_valid(): | ||
96 | project = project_form.save(commit=False) | ||
97 | project.owner = user | ||
98 | project.save() | ||
99 | for c in post_data.getlist('classifiers'): | ||
100 | classifier, created = Classifier.objects.get_or_create(name=c) | ||
101 | project.classifiers.add(classifier) | ||
102 | if files: | ||
103 | allow_overwrite = getattr(settings, | ||
104 | "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) | ||
105 | try: | ||
106 | release = Release.objects.get(version=post_data['version'], | ||
107 | project=project, | ||
108 | distribution=UPLOAD_TO + '/' + | ||
109 | files['distribution']._name) | ||
110 | if not allow_overwrite: | ||
111 | return HttpResponseForbidden(ALREADY_EXISTS_FMT % ( | ||
112 | release.filename, release)) | ||
113 | except Release.DoesNotExist: | ||
114 | release = None | ||
115 | |||
116 | # If the old file already exists, django will append a _ after the | ||
117 | # filename, however with .tar.gz files django does the "wrong" | ||
118 | # thing and saves it as project-0.1.2.tar_.gz. So remove it before | ||
119 | # django sees anything. | ||
120 | release_form = ReleaseForm(post_data, files, instance=release) | ||
121 | if release_form.is_valid(): | ||
122 | if release and os.path.exists(release.distribution.path): | ||
123 | os.remove(release.distribution.path) | ||
124 | release = release_form.save(commit=False) | ||
125 | release.project = project | ||
126 | release.save() | ||
127 | else: | ||
128 | return HttpResponseBadRequest( | ||
129 | "ERRORS: %s" % release_form.errors) | ||
130 | else: | ||
131 | return HttpResponseBadRequest("ERRORS: %s" % project_form.errors) | ||
132 | |||
133 | return HttpResponse() | ||
134 | |||
135 | |||
136 | def register_or_upload(request, post_data, files): | ||
137 | user = login_basic_auth(request) | ||
138 | if not user: | ||
139 | return HttpResponseUnauthorized('pypi') | ||
140 | |||
141 | login(request, user) | ||
142 | if not request.user.is_authenticated(): | ||
143 | return HttpResponseForbidden( | ||
144 | "Not logged in, or invalid username/password.") | ||
145 | |||
146 | return submit_project_or_release(user, post_data, files) | ||
147 | |||
148 | def create_user(request, post_data, files): | ||
149 | """Create new user from a distutil client request""" | ||
150 | form = RegistrationForm({"username": post_data["name"], | ||
151 | "email": post_data["email"], | ||
152 | "password1": post_data["password"], | ||
153 | "password2": post_data["password"]}) | ||
154 | if not form.is_valid(): | ||
155 | # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" | ||
156 | # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. | ||
157 | return HttpResponseBadRequest() | ||
158 | |||
159 | backend = get_backend("registration.backends.default.DefaultBackend") | ||
160 | if not backend.registration_allowed(request): | ||
161 | return HttpResponseBadRequest() | ||
162 | new_user = backend.register(request, **form.cleaned_data) | ||
163 | return HttpResponse("OK\n", status=200, mimetype='text/plain') | ||
164 | |||
165 | |||
166 | ACTIONS = { | ||
167 | # file_upload is the action used with distutils ``sdist`` command. | ||
168 | "file_upload": register_or_upload, | ||
169 | |||
170 | # submit is the :action used with distutils ``register`` command. | ||
171 | "submit": register_or_upload, | ||
172 | |||
173 | # user is the action used when registering a new user | ||
174 | "user": create_user, | ||
175 | } | ||
176 | |||
177 | |||
178 | def simple(request, template_name="djangopypi/simple.html"): | ||
179 | if request.method == "POST": | ||
180 | post_data, files = parse_distutils_request(request) | ||
181 | action_name = post_data.get(":action") | ||
182 | if action_name not in ACTIONS: | ||
183 | return HttpResponseNotImplemented( | ||
184 | "The action %s is not implemented" % action_name) | ||
185 | return ACTIONS[action_name](request, post_data, files) | ||
186 | |||
187 | dists = Project.objects.all().order_by("name") | ||
188 | context = RequestContext(request, { | ||
189 | "dists": dists, | ||
190 | "title": 'Package Index', | ||
191 | }) | ||
192 | |||
193 | return render_to_response(template_name, context_instance=context) | ||
194 | |||
195 | |||
196 | def show_links(request, dist_name, | ||
197 | template_name="djangopypi/show_links.html"): | ||
198 | try: | ||
199 | project = Project.objects.get(name=dist_name) | ||
200 | releases = project.releases.all().order_by('-version') | ||
201 | except Project.DoesNotExist: | ||
202 | raise Http404 | ||
203 | |||
204 | context = RequestContext(request, { | ||
205 | "dist_name": dist_name, | ||
206 | "releases": releases, | ||
207 | "project": project, | ||
208 | "title": project.name, | ||
209 | }) | ||
210 | |||
211 | return render_to_response(template_name, context_instance=context) | ||
212 | |||
213 | |||
214 | def show_version(request, dist_name, version, | ||
215 | template_name="djangopypi/show_version.html"): | ||
216 | try: | ||
217 | release = Project.objects.get(name=dist_name).releases \ | ||
218 | .get(version=version) | ||
219 | except Project.DoesNotExist: | ||
220 | raise Http404() | ||
221 | |||
222 | context = RequestContext(request, { | ||
223 | "dist_name": dist_name, | ||
224 | "version": version, | ||
225 | "release": release, | ||
226 | "title": dist_name, | ||
227 | }) | ||
228 | |||
229 | return render_to_response(template_name, context_instance=context) | ||
diff --git a/djangopypi/views/__init__.py b/djangopypi/views/__init__.py new file mode 100644 index 0000000..1438c54 --- /dev/null +++ b/djangopypi/views/__init__.py | |||
@@ -0,0 +1,76 @@ | |||
1 | from django.http import Http404 | ||
2 | from django.shortcuts import render_to_response | ||
3 | from django.template import RequestContext | ||
4 | |||
5 | from djangopypi.models import Project, Release | ||
6 | from djangopypi.http import HttpResponseNotImplemented | ||
7 | from djangopypi.http import parse_distutils_request | ||
8 | from djangopypi.views.dists import register_or_upload | ||
9 | from djangopypi.views.users import create_user | ||
10 | from djangopypi.views.search import search | ||
11 | |||
12 | |||
13 | ACTIONS = { | ||
14 | # file_upload is the action used with distutils ``sdist`` command. | ||
15 | "file_upload": register_or_upload, | ||
16 | |||
17 | # submit is the :action used with distutils ``register`` command. | ||
18 | "submit": register_or_upload, | ||
19 | |||
20 | # user is the action used when registering a new user | ||
21 | "user": create_user, | ||
22 | } | ||
23 | |||
24 | |||
25 | def simple(request, template_name="djangopypi/simple.html"): | ||
26 | if request.method == "POST": | ||
27 | post_data, files = parse_distutils_request(request) | ||
28 | action_name = post_data.get(":action") | ||
29 | if action_name not in ACTIONS: | ||
30 | return HttpResponseNotImplemented( | ||
31 | "The action %s is not implemented" % action_name) | ||
32 | return ACTIONS[action_name](request, post_data, files) | ||
33 | |||
34 | dists = Project.objects.all().order_by("name") | ||
35 | context = RequestContext(request, { | ||
36 | "dists": dists, | ||
37 | "title": 'Package Index', | ||
38 | }) | ||
39 | |||
40 | return render_to_response(template_name, context_instance=context) | ||
41 | |||
42 | |||
43 | def show_links(request, dist_name, | ||
44 | template_name="djangopypi/show_links.html"): | ||
45 | try: | ||
46 | project = Project.objects.get(name=dist_name) | ||
47 | releases = project.releases.all().order_by('-version') | ||
48 | except Project.DoesNotExist: | ||
49 | raise Http404 | ||
50 | |||
51 | context = RequestContext(request, { | ||
52 | "dist_name": dist_name, | ||
53 | "releases": releases, | ||
54 | "project": project, | ||
55 | "title": project.name, | ||
56 | }) | ||
57 | |||
58 | return render_to_response(template_name, context_instance=context) | ||
59 | |||
60 | |||
61 | def show_version(request, dist_name, version, | ||
62 | template_name="djangopypi/show_version.html"): | ||
63 | try: | ||
64 | project = Project.objects.get(name=dist_name) | ||
65 | release = project.releases.get(version=version) | ||
66 | except (Project.DoesNotExist, Release.DoesNotExist): | ||
67 | raise Http404() | ||
68 | |||
69 | context = RequestContext(request, { | ||
70 | "dist_name": dist_name, | ||
71 | "version": version, | ||
72 | "release": release, | ||
73 | "title": dist_name, | ||
74 | }) | ||
75 | |||
76 | return render_to_response(template_name, context_instance=context) | ||
diff --git a/djangopypi/views/dists.py b/djangopypi/views/dists.py new file mode 100644 index 0000000..9e4a146 --- /dev/null +++ b/djangopypi/views/dists.py | |||
@@ -0,0 +1,79 @@ | |||
1 | import os | ||
2 | |||
3 | from django.conf import settings | ||
4 | from django.http import (HttpResponse, HttpResponseForbidden, | ||
5 | HttpResponseBadRequest) | ||
6 | from django.utils.translation import ugettext_lazy as _ | ||
7 | from django.contrib.auth import login | ||
8 | |||
9 | from djangopypi.http import login_basic_auth, HttpResponseUnauthorized | ||
10 | from djangopypi.forms import ProjectForm, ReleaseForm | ||
11 | from djangopypi.models import Project, Release, Classifier, UPLOAD_TO | ||
12 | |||
13 | ALREADY_EXISTS_FMT = _( | ||
14 | "A file named '%s' already exists for %s. Please create a new release.") | ||
15 | |||
16 | |||
17 | def submit_project_or_release(user, post_data, files): | ||
18 | """Registers/updates a project or release""" | ||
19 | try: | ||
20 | project = Project.objects.get(name=post_data['name']) | ||
21 | if project.owner != user: | ||
22 | return HttpResponseForbidden( | ||
23 | "That project is owned by someone else!") | ||
24 | except Project.DoesNotExist: | ||
25 | project = None | ||
26 | |||
27 | project_form = ProjectForm(post_data, instance=project) | ||
28 | if project_form.is_valid(): | ||
29 | project = project_form.save(commit=False) | ||
30 | project.owner = user | ||
31 | project.save() | ||
32 | for c in post_data.getlist('classifiers'): | ||
33 | classifier, created = Classifier.objects.get_or_create(name=c) | ||
34 | project.classifiers.add(classifier) | ||
35 | if files: | ||
36 | allow_overwrite = getattr(settings, | ||
37 | "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) | ||
38 | try: | ||
39 | release = Release.objects.get(version=post_data['version'], | ||
40 | project=project, | ||
41 | distribution=UPLOAD_TO + '/' + | ||
42 | files['distribution']._name) | ||
43 | if not allow_overwrite: | ||
44 | return HttpResponseForbidden(ALREADY_EXISTS_FMT % ( | ||
45 | release.filename, release)) | ||
46 | except Release.DoesNotExist: | ||
47 | release = None | ||
48 | |||
49 | # If the old file already exists, django will append a _ after the | ||
50 | # filename, however with .tar.gz files django does the "wrong" | ||
51 | # thing and saves it as project-0.1.2.tar_.gz. So remove it before | ||
52 | # django sees anything. | ||
53 | release_form = ReleaseForm(post_data, files, instance=release) | ||
54 | if release_form.is_valid(): | ||
55 | if release and os.path.exists(release.distribution.path): | ||
56 | os.remove(release.distribution.path) | ||
57 | release = release_form.save(commit=False) | ||
58 | release.project = project | ||
59 | release.save() | ||
60 | else: | ||
61 | return HttpResponseBadRequest( | ||
62 | "ERRORS: %s" % release_form.errors) | ||
63 | else: | ||
64 | return HttpResponseBadRequest("ERRORS: %s" % project_form.errors) | ||
65 | |||
66 | return HttpResponse() | ||
67 | |||
68 | |||
69 | def register_or_upload(request, post_data, files): | ||
70 | user = login_basic_auth(request) | ||
71 | if not user: | ||
72 | return HttpResponseUnauthorized('pypi') | ||
73 | |||
74 | login(request, user) | ||
75 | if not request.user.is_authenticated(): | ||
76 | return HttpResponseForbidden( | ||
77 | "Not logged in, or invalid username/password.") | ||
78 | |||
79 | return submit_project_or_release(user, post_data, files) | ||
diff --git a/djangopypi/views/search.py b/djangopypi/views/search.py new file mode 100644 index 0000000..5d6a76b --- /dev/null +++ b/djangopypi/views/search.py | |||
@@ -0,0 +1,24 @@ | |||
1 | from django.template import RequestContext | ||
2 | from django.shortcuts import render_to_response | ||
3 | from django.db.models.query import Q | ||
4 | |||
5 | from djangopypi.models import Project | ||
6 | |||
7 | |||
8 | def _search_query(q): | ||
9 | return Q(name__contains=q) | Q(summary__contains=q) | ||
10 | |||
11 | |||
12 | def search(request, template="djangopypi/search_results.html"): | ||
13 | context = RequestContext(request, {"dists": None, "search_term": ""}) | ||
14 | |||
15 | if request.method == "POST": | ||
16 | search_term = context["search_term"] = request.POST.get("search_term") | ||
17 | if search_term: | ||
18 | query = _search_query(search_term) | ||
19 | context["dists"] = Project.objects.filter(query) | ||
20 | |||
21 | if context["dists"] is None: | ||
22 | context["dists"] = Project.objects.all() | ||
23 | |||
24 | return render_to_response(template, context_instance=context) | ||
diff --git a/djangopypi/views/users.py b/djangopypi/views/users.py new file mode 100644 index 0000000..a58ac3e --- /dev/null +++ b/djangopypi/views/users.py | |||
@@ -0,0 +1,24 @@ | |||
1 | from django.http import HttpResponse, HttpResponseBadRequest | ||
2 | |||
3 | from registration.forms import RegistrationForm | ||
4 | from registration.backends import get_backend | ||
5 | |||
6 | DEFAULT_BACKEND = "registration.backends.default.DefaultBackend" | ||
7 | |||
8 | |||
9 | def create_user(request, post_data, files, backend_name=DEFAULT_BACKEND): | ||
10 | """Create new user from a distutil client request""" | ||
11 | form = RegistrationForm({"username": post_data["name"], | ||
12 | "email": post_data["email"], | ||
13 | "password1": post_data["password"], | ||
14 | "password2": post_data["password"]}) | ||
15 | if not form.is_valid(): | ||
16 | # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" | ||
17 | # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. | ||
18 | return HttpResponseBadRequest() | ||
19 | |||
20 | backend = get_backend(backend_name) | ||
21 | if not backend.registration_allowed(request): | ||
22 | return HttpResponseBadRequest() | ||
23 | new_user = backend.register(request, **form.cleaned_data) | ||
24 | return HttpResponse("OK\n", status=200, mimetype='text/plain') | ||