nautilebleu / django_hg
django_hg allows managing (create, authenticate, clone/push/pull…) Mercurial repositories throught django.
$ hg clone http://bitbucket.org/nautilebleu/django_hg/
| commit 31: | e258ed471d54 |
| parent 30: | 8d6bb0d91835 |
| branch: | default |
Changed (Δ7.6 KB):
docs/build/index.html (48 lines added, 8 lines removed)
docs/source/index.txt (48 lines added, 7 lines removed)
forms.py (30 lines added, 2 lines removed)
models.py (30 lines added, 9 lines removed)
site_media/css/django_hg.css (0 lines added, 10 lines removed)
templates/django_hg/changeset_info.html (1 lines added, 1 lines removed)
templates/django_hg/list.html (16 lines added, 11 lines removed)
templatetags/django_hg_tags.py (62 lines added, 3 lines removed)
views.py (17 lines added, 2 lines removed)
Up to file-list docs/build/index.html:
21 |
21 |
<li><a href="#install">Install</a></li> |
22 |
22 |
<li><a href="#usage">Usage</a><ul> |
23 |
23 |
<li><a href="#administration">Administration</a></li> |
24 |
<li><a href="#public">Public</a>< |
|
24 |
<li><a href="#public">Public</a><ul> |
|
25 |
<li><a href="#list">List</a></li> |
|
26 |
<li><a href="#repository">Repository</a></li> |
|
27 |
<li><a href="#file">File</a></li> |
|
28 |
</ul> |
|
29 |
</li> |
|
25 |
30 |
<li><a href="#hg-commands-clone-push-and-pull">Hg commands (clone, push and pull)</a></li> |
26 |
31 |
</ul> |
27 |
32 |
</li> |
33 |
<li><a href="#templating">Templating</a></li> |
|
28 |
34 |
<li><a href="#deployment">Deployment</a></li> |
29 |
35 |
<li><a href="#to-dos">To-dos</a></li> |
30 |
36 |
<li><a href="#requirements">Requirements</a></li> |
| … | … | @@ -69,6 +75,10 @@ and install is quite easy:</p> |
69 |
75 |
<span class="s">"private"</span><span class="p">:</span> <span class="s">"/Users/goulwen/Repositories/private/"</span> |
70 |
76 |
<span class="p">}</span> |
71 |
77 |
<span class="n">DJANGO_HG_PAGER_ITEMS</span> <span class="o">=</span> <span class="mf">30</span> |
78 |
<span class="c"># one of pygment styles : autumn, borland, bw, colorful, default, emacs,</span> |
|
79 |
<span class="c"># friendly, fruity, manni, murphy, native, pastie, perldoc, tango, trac, vim,</span> |
|
80 |
<span class="c"># vs</span> |
|
81 |
<span class="n">DJANGO_HG_PYGMENT_STYLE</span> <span class="o">=</span> <span class="s">'tango'</span> |
|
72 |
82 |
|
73 |
83 |
<span class="c"># templates dir</span> |
74 |
84 |
<span class="n">TEMPLATE_DIRS</span> <span class="o">=</span> <span class="p">(</span> |
| … | … | @@ -113,6 +123,7 @@ roles <code>Read</code>, <code>Read/Writ |
113 |
123 |
repositories in browsers and when handling commands like <code>clone</code>, <code>push</code> and |
114 |
124 |
<code>pull</code>.</p> |
115 |
125 |
<h3 id="public">Public</h3> |
126 |
<h4 id="list">List</h4> |
|
116 |
127 |
<p>django_hg lets you browse a list of repositories, depending of the user |
117 |
128 |
permissions:</p> |
118 |
129 |
<ul> |
| … | … | @@ -120,15 +131,42 @@ permissions:</p> |
120 |
131 |
<li>If the user is authenticated, private repositories where the user has role are |
121 |
132 |
displayed, plus public ones.</li> |
122 |
133 |
</ul> |
123 |
<p>The repository view displays informations about the latest revision (aka <code>tip</code> |
|
124 |
in Mercurial) at the top of the page and below, the top level of the repository. |
|
125 |
You can browse the repository or display the changelog.</p> |
|
126 |
<p>Once in the changelog, you can browse the repository manifest at this given |
|
127 |
revision.</p> |
|
128 |
<p>When browsing the repository, you can view the filelog of a given file.</p> |
|
134 |
<p>A search facility is available. Currently, search looks in repositories' title |
|
135 |
and summary, but not in the source code or the changelog.</p> |
|
136 |
<p>Results can also be filtered using current user permissions, so only |
|
137 |
repositories he owned are displayed, for example.</p> |
|
138 |
<h4 id="repository">Repository</h4> |
|
139 |
<p>When accessing a repository, you can display 3 differents views:</p> |
|
140 |
<ul> |
|
141 |
<li>The <code>overview</code> view summarizes the repository, showing the latest changeset |
|
142 |
(aka <code>tip</code> in Mercurial, people involved in the project. This page will evolve |
|
143 |
in the next weeks to be raffined.</li> |
|
144 |
<li>The <code>browse</code> view displays the repository at a given revision (<code>tip</code> by default)</li> |
|
145 |
<li>The <code>changesets</code>view diplays the list of revisions of the repository. |
|
146 |
You can browse the repository at a given revision or display the changelog |
|
147 |
details.</li> |
|
148 |
</ul> |
|
149 |
<h4 id="file">File</h4> |
|
150 |
<p>From the <code>browse</code> and the <code>changeset</code> views, repository files can be accessed:</p> |
|
151 |
<ul> |
|
152 |
<li>The <code>log</code> view displays changes history of the file.</li> |
|
153 |
<li>The <code>view</code> view shows the file, depending of its mimetype:</li> |
|
154 |
<li>If a <a href="http://pygments.org">Pygments [en]</a> lexer can be found, the file is displayed |
|
155 |
with syntax coloration</li> |
|
156 |
<li>If the mimetype corresponds to a picture (PNG, GIF, JPEG) or a PDF, the file |
|
157 |
is displayed</li> |
|
158 |
<li>Otherwise, you can download it or force the display a plain text.</li> |
|
159 |
<li>The <code>diff</code> view will allow the comparison between two revisions, but is far |
|
160 |
from being operational yet.</li> |
|
161 |
</ul> |
|
129 |
162 |
<h3 id="hg-commands-clone-push-and-pull">Hg commands (clone, push and pull)</h3> |
130 |
163 |
<p>django_hg supports <code>clone</code>, <code>push</code> and <code>pull</code> over HTTP throught django, |
131 |
164 |
including authentication.</p> |
165 |
<h2 id="templating">Templating</h2> |
|
166 |
<p>django_hg tries to follow |
|
167 |
<a href="http://ericholscher.com/projects/django-conventions/app/">django reusable apps conventions [en]</a> in naming of |
|
168 |
blocks.</p> |
|
169 |
<p>A simple CSS is provided in the <code>site_media</code> directory. Feel free to</p> |
|
132 |
170 |
<h2 id="deployment">Deployment</h2> |
133 |
171 |
<p>Because django_hg relies on wsgi objects to performs Mercurial commands, django |
134 |
172 |
must be deployed throught <a href="http://docs.djangoproject.com/en/1.0/howto/deployment/modwsgi/">mod_wsgi [en]</a>.</p> |
| … | … | @@ -140,7 +178,8 @@ order to <a href="http://www.arnebrodows |
140 |
178 |
|
141 |
179 |
<h2 id="to-dos">To-dos</h2> |
142 |
180 |
<ul> |
143 |
<li>Add missing features such as search in |
|
181 |
<li>Add missing features such as search in list, changesets, filelog</li> |
|
182 |
<li><code>diff</code> and <code>archive</code></li> |
|
144 |
183 |
<li>Add Ajax browsing for the repository</li> |
145 |
184 |
</ul> |
146 |
185 |
<h2 id="requirements">Requirements</h2> |
| … | … | @@ -148,6 +187,7 @@ order to <a href="http://www.arnebrodows |
148 |
187 |
<li><a href="http://python.org/">Python 2.5+ [en]</a></li> |
149 |
188 |
<li><a href="http://selenic.com/mercurial/">Mercurial 1.2 [en]</a></li> |
150 |
189 |
<li><a href="http://djangoproject.com/">django 1.0.2 [en]</a></li> |
190 |
<li><a href="http://pygments.org">Pygments [en]</a></li> |
|
151 |
191 |
</ul> |
152 |
192 |
<p>For deployement:</p> |
153 |
193 |
<ul> |
Up to file-list docs/source/index.txt:
| … | … | @@ -46,6 +46,10 @@ Configure your `settings.py`: |
46 |
46 |
"private": "/Users/goulwen/Repositories/private/" |
47 |
47 |
} |
48 |
48 |
DJANGO_HG_PAGER_ITEMS = 30 |
49 |
# one of pygment styles : autumn, borland, bw, colorful, default, emacs, |
|
50 |
# friendly, fruity, manni, murphy, native, pastie, perldoc, tango, trac, vim, |
|
51 |
# vs |
|
52 |
DJANGO_HG_PYGMENT_STYLE = 'tango' |
|
49 |
53 |
|
50 |
54 |
# templates dir |
51 |
55 |
TEMPLATE_DIRS = ( |
| … | … | @@ -98,6 +102,8 @@ repositories in browsers and when handli |
98 |
102 |
|
99 |
103 |
### Public |
100 |
104 |
|
105 |
#### List |
|
106 |
||
101 |
107 |
django_hg lets you browse a list of repositories, depending of the user |
102 |
108 |
permissions: |
103 |
109 |
|
| … | … | @@ -105,20 +111,52 @@ permissions: |
105 |
111 |
* If the user is authenticated, private repositories where the user has role are |
106 |
112 |
displayed, plus public ones. |
107 |
113 |
|
108 |
The repository view displays informations about the latest revision (aka `tip` |
|
109 |
in Mercurial) at the top of the page and below, the top level of the repository. |
|
110 |
You can browse the repository or display the changelog. |
|
114 |
A search facility is available. Currently, search looks in repositories' title |
|
115 |
and summary, but not in the source code or the changelog. |
|
111 |
116 |
|
112 |
Once in the changelog, you can browse the repository manifest at this given |
|
113 |
revision. |
|
117 |
Results can also be filtered using current user permissions, so only |
|
118 |
repositories he owned are displayed, for example. |
|
114 |
119 |
|
115 |
When browsing the repository, you can view the filelog of a given file. |
|
120 |
#### Repository |
|
121 |
||
122 |
When accessing a repository, you can display 3 differents views: |
|
123 |
||
124 |
* The `overview` view summarizes the repository, showing the latest changeset |
|
125 |
(aka `tip` in Mercurial, people involved in the project. This page will evolve |
|
126 |
in the next weeks to be raffined. |
|
127 |
* The `browse` view displays the repository at a given revision (`tip` by default) |
|
128 |
* The `changesets`view diplays the list of revisions of the repository. |
|
129 |
You can browse the repository at a given revision or display the changelog |
|
130 |
details. |
|
131 |
||
132 |
#### File |
|
133 |
||
134 |
From the `browse` and the `changeset` views, repository files can be accessed: |
|
135 |
||
136 |
* The `log` view displays changes history of the file. |
|
137 |
* The `view` view shows the file, depending of its mimetype: |
|
138 |
* If a [Pygments [en]][_pygments] lexer can be found, the file is displayed |
|
139 |
with syntax coloration |
|
140 |
* If the mimetype corresponds to a picture (PNG, GIF, JPEG) or a PDF, the file |
|
141 |
is displayed |
|
142 |
* Otherwise, you can download it or force the display a plain text. |
|
143 |
* The `diff` view will allow the comparison between two revisions, but is far |
|
144 |
from being operational yet. |
|
116 |
145 |
|
117 |
146 |
### Hg commands (clone, push and pull) |
118 |
147 |
|
119 |
148 |
django_hg supports `clone`, `push` and `pull` over HTTP throught django, |
120 |
149 |
including authentication. |
121 |
150 |
|
151 |
## Templating |
|
152 |
||
153 |
django_hg tries to follow |
|
154 |
[django reusable apps conventions [en]][_django_reusable_apps] in naming of |
|
155 |
blocks. |
|
156 |
||
157 |
A simple CSS is provided in the `site_media` directory. Feel free to |
|
158 |
||
159 |
||
122 |
160 |
## Deployment |
123 |
161 |
|
124 |
162 |
Because django_hg relies on wsgi objects to performs Mercurial commands, django |
| … | … | @@ -132,7 +170,8 @@ order to [pass authentication [en]][_wsg |
132 |
170 |
|
133 |
171 |
## To-dos |
134 |
172 |
|
135 |
* Add missing features such as search in |
|
173 |
* Add missing features such as search in list, changesets, filelog |
|
174 |
* `diff` and `archive` |
|
136 |
175 |
* Add Ajax browsing for the repository |
137 |
176 |
|
138 |
177 |
## Requirements |
| … | … | @@ -140,6 +179,7 @@ order to [pass authentication [en]][_wsg |
140 |
179 |
* [Python 2.5+ [en]][_python] |
141 |
180 |
* [Mercurial 1.2 [en]][_mercurial] |
142 |
181 |
* [django 1.0.2 [en]][_django] |
182 |
* [Pygments [en]][_pygments] |
|
143 |
183 |
|
144 |
184 |
For deployement: |
145 |
185 |
|
| … | … | @@ -162,5 +202,6 @@ For deployement: |
162 |
202 |
[_mysql]: http://www.mysql.com/ |
163 |
203 |
[_postgresql]: http://www.postgresql.org/ |
164 |
204 |
[_python]: http://python.org/ |
205 |
[_pygments]: http://pygments.org |
|
165 |
206 |
[_mod_wsgi]: http://code.google.com/p/modwsgi/ |
166 |
207 |
[_wsgipassauthorization]: http://www.arnebrodowski.de/blog/508-Django,-mod_wsgi-and-HTTP-Authentication.html |
1 |
1 |
# coding=utf-8 |
2 |
#!/usr/bin/env python |
|
3 |
2 |
from django import forms |
4 |
|
|
3 |
#from django_hg.models import HgRepository |
|
4 |
||
5 |
DISPLAY_CHOICES = ( |
|
6 |
('all', 'All'), |
|
7 |
('only_member', 'Only repositories I contribute'), |
|
8 |
('only_owner', 'Only repositories I own') |
|
9 |
) |
|
10 |
||
11 |
class HgRepositoryForm(forms.Form): |
|
12 |
search = forms.CharField(max_length=100, required=False) |
|
13 |
display = forms.CharField(max_length=11, |
|
14 |
required=False, |
|
15 |
widget= forms.Select(choices=DISPLAY_CHOICES)) |
|
16 |
||
17 |
def clean_display(self): |
|
18 |
data = self.data['display'] |
|
19 |
return data |
|
20 |
||
21 |
def clean_search(self): |
|
22 |
data = self.data['search'] |
|
23 |
#print 'dans clean_name' + str(data) |
|
24 |
||
25 |
return data |
|
26 |
||
27 |
def is_valid(self): |
|
28 |
#print 'avant' |
|
29 |
#print self['search'].data |
|
30 |
#self.clean() |
|
31 |
#print 'apres' |
|
32 |
return True |
3 |
3 |
from mercurial import ui, hg |
4 |
4 |
from django.db import models |
5 |
5 |
from django.contrib.auth.models import User |
6 |
from django.db.models import Q |
|
6 |
7 |
from django.utils.translation import ugettext_lazy as _ |
7 |
8 |
from django.core.urlresolvers import reverse |
8 |
9 |
from django.conf import settings as global_settings |
| … | … | @@ -28,8 +29,10 @@ class HgFile(): |
28 |
29 |
def __init__(self, name, path, hg_view): |
29 |
30 |
self.name = name |
30 |
31 |
self.path = path + name |
32 |
||
31 |
33 |
if self.name.find('/')>-1: |
32 |
34 |
self.is_dir = True |
35 |
#self.size = self.size |
|
33 |
36 |
else: |
34 |
37 |
self.is_dir = False |
35 |
38 |
fctx = hg_view.ctx.filectx(self.path) |
| … | … | @@ -85,6 +88,10 @@ class HgContext(): |
85 |
88 |
names.append(remain) |
86 |
89 |
else: |
87 |
90 |
d = remain.split('/') |
91 |
#print d[len(d)-1] |
|
92 |
#fctx = self.ctx.filectx(remain) |
|
93 |
#print fctx.size() |
|
94 |
||
88 |
95 |
if (d[0] not in dirs) : |
89 |
96 |
dirs.append(d[0]) |
90 |
97 |
names.append(d[0] + '/') |
| … | … | @@ -102,34 +109,48 @@ class HgContext(): |
102 |
109 |
|
103 |
110 |
|
104 |
111 |
class HgRepositoryManager(models.Manager): |
105 |
def filter_for_user(self, user, permission |
|
112 |
def filter_for_user(self, user, permission, **kwargs): |
|
113 |
qs = HgRepository.objects |
|
114 |
# search filtering |
|
115 |
for arg in kwargs: |
|
116 |
if arg == 'search' and kwargs[arg] != '' and kwargs[arg] is not None: |
|
117 |
for keyword in kwargs[arg].split(' '): |
|
118 |
qs = qs.filter(Q(name__icontains=keyword) | Q(summary__icontains=keyword)) |
|
119 |
if arg == 'display' and kwargs['display'] != 'all': |
|
120 |
if kwargs[arg] == 'only_member': |
|
121 |
qs = qs.filter(repositoryuser__user__id = user.id, |
|
122 |
repositoryuser__permission__gte=1) |
|
123 |
elif kwargs[arg] == 'only_owner': |
|
124 |
qs = qs.filter(repositoryuser__user__id = user.id, |
|
125 |
repositoryuser__permission=7) |
|
126 |
||
106 |
127 |
if user.is_authenticated(): |
107 |
return HgRepository.objects.distinct().extra( |
|
108 |
where=['anonymous_access=True or ' |
|
128 |
return qs.distinct().extra( |
|
129 |
where=['(anonymous_access=True or ' |
|
109 |
130 |
+ '(anonymous_access=False ' |
110 |
131 |
+ 'and django_hg_repositoryuser.user_id=%s ' |
111 |
132 |
+ 'and django_hg_repositoryuser.source_repository_id=django_hg_hgrepository.id ' |
112 |
+ 'and django_hg_repositoryuser.permission>=%s) |
|
133 |
+ 'and django_hg_repositoryuser.permission>=%s))'], |
|
113 |
134 |
params= [user.id, permission], |
114 |
135 |
tables=['django_hg_repositoryuser'] |
115 |
136 |
) |
116 |
137 |
else: |
117 |
return |
|
138 |
return qs.filter(anonymous_access=True) |
|
118 |
139 |
|
119 |
def count_for_user(self, user, permission=3 |
|
140 |
def count_for_user(self, user, permission=3, **kwargs): |
|
120 |
141 |
""" |
121 |
142 |
Return the total number of source repositories which the user has at |
122 |
143 |
least the given permission level |
123 |
144 |
""" |
124 |
return self.filter_for_user(user, permission |
|
145 |
return self.filter_for_user(user, permission, **kwargs).count() |
|
125 |
146 |
|
126 |
147 |
|
127 |
def get_for_user(self, user, permission=3 |
|
148 |
def get_for_user(self, user, permission=3, **kwargs): |
|
128 |
149 |
""" |
129 |
150 |
Return a list of source repositories which the user has at least the |
130 |
151 |
given permission level |
131 |
152 |
""" |
132 |
return self.filter_for_user(user, permission |
|
153 |
return self.filter_for_user(user, permission, **kwargs).order_by('name') |
|
133 |
154 |
|
134 |
155 |
|
135 |
156 |
class HgRepository(models.Model): |
Up to file-list site_media/css/django_hg.css:
| … | … | @@ -52,16 +52,6 @@ div.diff { |
52 |
52 |
background: #FFDDDD; |
53 |
53 |
} |
54 |
54 |
|
55 |
.django_hg_toolbar { |
|
56 |
float: right ; |
|
57 |
padding: 0px 5px ; |
|
58 |
width: 360px ; |
|
59 |
} |
|
60 |
||
61 |
.django_hg_toolbar dl dd { |
|
62 |
margin: 0px |
|
63 |
} |
|
64 |
||
65 |
55 |
.file { |
66 |
56 |
display: inline-block ; |
67 |
57 |
} |
Up to file-list templates/django_hg/changeset_info.html:
2 |
2 |
{% load django_hg_tags %} |
3 |
3 |
|
4 |
4 |
<dl> |
5 |
{% blocktrans with ctx.date|format_hg_date as date and ctx.user |
|
5 |
{% blocktrans with ctx.date|format_hg_date as date and ctx.user|format_hg_user as user %} |
|
6 |
6 |
<dt>On {{ date }}, {{ user }} has committed |
7 |
7 |
{% endblocktrans %} |
8 |
8 |
<a href="{% url hg-changeset repo ctx.rev %}"> |
Up to file-list templates/django_hg/list.html:
33 |
33 |
<div class="repo_views"></div> |
34 |
34 |
|
35 |
35 |
<div id="django_hg_container"> |
36 |
<div |
|
36 |
<div id="repo_bar"> |
|
37 |
<form action="{% url hg-list %}" method="get"> |
|
37 |
38 |
<ul> |
39 |
{{ form }} |
|
38 |
40 |
<li> |
39 |
<dl> |
|
40 |
<dt>Access</dt> |
|
41 |
<dd> |
|
42 |
<select> |
|
43 |
<option value="">{% trans 'Public or private' %}</option> |
|
44 |
<option value="0">{% trans 'Public' %}</option> |
|
45 |
<option value="0">{% trans 'Private' %}</option> |
|
46 |
</select> |
|
47 |
</dd> |
|
48 |
</dl> |
|
41 |
<input type="submit" value="OK" /> |
|
49 |
42 |
</li> |
50 |
43 |
</ul> |
44 |
</form> |
|
51 |
45 |
</div> |
46 |
{% trans "You can search within repositories' names and summary, but not in their content nor their revisions" %} |
|
52 |
47 |
|
53 |
48 |
<div id="django_hg_main"> |
54 |
49 |
<ul class="pager"> |
65 |
60 |
</span> |
66 |
61 |
</h3> |
67 |
62 |
<p class="description">{{ repo.summary }}</p> |
63 |
<p class="float_left"> |
|
64 |
{% for member in repo.members %} |
|
65 |
{% ifchanged member.permission %} |
|
66 |
<em>{{ member.get_permission_display|capfirst }}</em><br /> |
|
67 |
{% endifchanged %} |
|
68 |
- {{ member.user }}<br /> |
|
69 |
{% endfor %} |
|
70 |
||
71 |
||
72 |
</p> |
|
68 |
73 |
<ul class="changeset"> |
69 |
74 |
{% with repo.get_context as ctx %} |
70 |
75 |
{% include 'django_hg/changeset_info.html' %} |
1 |
1 |
# coding=utf-8 |
2 |
2 |
from datetime import datetime |
3 |
import pytz |
|
4 |
#from pytz import timezone |
|
3 |
5 |
from math import ceil |
4 |
6 |
|
5 |
7 |
from django.core.urlresolvers import reverse |
8 |
from django.conf import settings as global_settings |
|
6 |
9 |
from django.template import Library |
7 |
10 |
|
11 |
||
8 |
12 |
register = Library() |
9 |
13 |
|
10 |
14 |
@register.inclusion_tag('django_hg/path.html', takes_context=True) |
| … | … | @@ -60,6 +64,11 @@ def path(context): |
60 |
64 |
|
61 |
65 |
@register.inclusion_tag('django_hg/filedisplay.html', takes_context=True) |
62 |
66 |
def filedisplay(context): |
67 |
""" |
|
68 |
Display a file at a given revision. |
|
69 |
According to the mimetype, the file may be displayed as source, colored |
|
70 |
thanks to Pygments, or, for pictures and PDF, directly in the browser |
|
71 |
""" |
|
63 |
72 |
from pygments import highlight |
64 |
73 |
from pygments.formatters import HtmlFormatter |
65 |
74 |
from pygments.lexers import get_lexer_for_mimetype, guess_lexer_for_filename |
| … | … | @@ -107,9 +116,46 @@ def format_hg_date(value): |
107 |
116 |
As for now, the tz is not handled |
108 |
117 |
|
109 |
118 |
>>> format_hg_date([1234567890, -7200]) |
110 |
'2009-02-14 0 |
|
119 |
'2009-02-14 02:31:30+01:00' |
|
111 |
120 |
""" |
112 |
|
|
121 |
django_tz = pytz.timezone(global_settings.TIME_ZONE) |
|
122 |
||
123 |
return '%(datetime)s' % { 'datetime': django_tz.localize(datetime.fromtimestamp(value[0]-value[1])) } |
|
124 |
||
125 |
@register.filter(name='format_hg_user') |
|
126 |
def format_hg_user(value): |
|
127 |
""" |
|
128 |
Return the user name from the Mercurial committer. Especially, try to hide |
|
129 |
the e-mail address if present |
|
130 |
>>> format_hg_user('Foo') |
|
131 |
'Foo' |
|
132 |
||
133 |
>>> format_hg_user('Foo bar') |
|
134 |
'Foo bar' |
|
135 |
||
136 |
>>> format_hg_user('Foo bar <foo@bar.com>') |
|
137 |
'Foo bar' |
|
138 |
||
139 |
>>> format_hg_user('Foo bar <foo@bar.com>, Baz <baz@bar.com>') |
|
140 |
'Foo bar, Baz' |
|
141 |
||
142 |
>>> format_hg_user('Foo, Bar, Baz') |
|
143 |
'Foo, Bar, Baz' |
|
144 |
""" |
|
145 |
if value.find(',')> -1: |
|
146 |
elts = value.split(', ') |
|
147 |
else: |
|
148 |
elts = [value] |
|
149 |
names = [] |
|
150 |
for elt in elts: |
|
151 |
parts = elt.split(' ') |
|
152 |
name = [] |
|
153 |
for part in parts: |
|
154 |
if not part.startswith('<'): |
|
155 |
name.append(part) |
|
156 |
names.append(' '.join(name)) |
|
157 |
||
158 |
return ', '.join(names) |
|
113 |
159 |
|
114 |
160 |
@register.inclusion_tag('django_hg/pagination.html', takes_context=True) |
115 |
161 |
def paginate(context): |
| … | … | @@ -210,7 +256,20 @@ def paginate(context): |
210 |
256 |
|
211 |
257 |
@register.filter |
212 |
258 |
def strip_path(value): |
213 |
""" |
|
259 |
""" |
|
260 |
A pythonic version of PHP basename |
|
261 |
>>> strip_path('foo') |
|
262 |
'foo' |
|
263 |
||
264 |
>>> strip_path('foo/bar') |
|
265 |
'bar' |
|
266 |
||
267 |
>>> strip_path('foo/bar/') |
|
268 |
'bar' |
|
269 |
""" |
|
270 |
if value.rfind('/') == len(value)-1: |
|
271 |
value = value[:value.rfind('/')] |
|
272 |
||
214 |
273 |
return value[value.rfind('/')+1:] |
215 |
274 |
|
216 |
275 |
if __name__ == "__main__": |
| … | … | @@ -12,6 +12,7 @@ from django.conf import settings as glob |
12 |
12 |
from django.template import RequestContext |
13 |
13 |
from django_hg.models import HgContext, HgRepository |
14 |
14 |
from django_hg.decorators import logged_in_or_basicauth |
15 |
from django_hg.forms import HgRepositoryForm |
|
15 |
16 |
|
16 |
17 |
from mercurial.error import RepoError |
17 |
18 |
|
| … | … | @@ -282,10 +283,22 @@ def list(request): |
282 |
283 |
only public repositories |
283 |
284 |
|
284 |
285 |
""" |
286 |
#if request.GET.get('search'): |
|
287 |
form = HgRepositoryForm({'search': request.GET.get('search'), |
|
288 |
'display': request.GET.get('display')}) |
|
289 |
if form.is_valid(): |
|
290 |
search = form.data['search'] |
|
291 |
display = form.data['display'] |
|
292 |
#'?search='+form.cleaned_data['name'] + |
|
293 |
#'&anonymous_access=' + str(form.cleaned_data['anonymous_access'])) |
|
294 |
else: |
|
295 |
search = None |
|
296 |
display= 'all' |
|
297 |
||
285 |
298 |
page = int(request.GET.get('page', 1)) |
286 |
299 |
if page <= 0 : |
287 |
300 |
page = 1 |
288 |
max = HgRepository.objects.count_for_user(request.user, 1 |
|
301 |
max = HgRepository.objects.count_for_user(request.user, 1, search=search, display=display) |
|
289 |
302 |
if ceil(float(max)/float(global_settings.DJANGO_HG_PAGER_ITEMS))<page: |
290 |
303 |
page = int(ceil(float(max)/float(global_settings.DJANGO_HG_PAGER_ITEMS))) |
291 |
304 |
start = (page-1)*global_settings.DJANGO_HG_PAGER_ITEMS |
| … | … | @@ -295,7 +308,7 @@ def list(request): |
295 |
308 |
|
296 |
309 |
repositories = [] |
297 |
310 |
if (max > 0): |
298 |
q = HgRepository.objects.get_for_user(request.user, 1 |
|
311 |
q = HgRepository.objects.get_for_user(request.user, 1, search=search, display=display)[start:end] |
|
299 |
312 |
for repo in q: |
300 |
313 |
try: |
301 |
314 |
repo.set_context(HgContext(repo, 'tip')) |
| … | … | @@ -307,12 +320,14 @@ def list(request): |
307 |
320 |
except: |
308 |
321 |
pass |
309 |
322 |
|
323 |
||
310 |
324 |
return render_to_response('django_hg/list.html', { |
311 |
325 |
'end': end, |
312 |
326 |
'host': request.META['HTTP_HOST'], |
313 |
327 |
'items_per_page': global_settings.DJANGO_HG_PAGER_ITEMS, |
314 |
328 |
'repositories': repositories, |
315 |
329 |
'page': page, |
330 |
'form': form.as_ul(), |
|
316 |
331 |
'start': start+1, |
317 |
332 |
'max': int(max), |
318 |
333 |
}, context_instance=RequestContext(request)) |
