Skip to content
This repository

Adjust the 'attachments' property of Documents to find all files. #345

Closed
wants to merge 1 commit into from

4 participants

David Walsh luke crouch James Bennett Les Orchard
David Walsh
Collaborator

This uses pretty simple logic to scan a Document's HTML for both
MindTouch and kuma file URL patterns, and then uses Q objects to build
a correct query to return the corresponding Attachment objects. If no
file URLs are found in the Document, it'll return an empty Attachment
queryset.

fix bug 770397 - Displaying attachments table under document and tags

fix bug 770563 - Show tags and file attachments regardless of TOC

Bugfix: actually wrap the file queries in Q objects.

Changing file URL method

David Walsh
Collaborator

This is a consolidated, merged PR of:

#339

and:

#343

David Walsh
Collaborator

Fix file size duplication.

apps/wiki/tests/test_views.py
... ...
@@ -1846,4 +1847,111 @@ def test_legacy_redirect(self):
1846 1847
                                             'filename': f['filename']})
1847 1848
             resp = self.client.get(mindtouch_url)
1848 1849
             eq_(301, resp.status_code)
  1850
+<<<<<<< HEAD
1
Les Orchard Collaborator
lmorchard added a note July 05, 2012

Merge markers - bad stuff here! Can't merge with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/wiki/tests/test_views.py
((5 lines not shown))
1849 1851
             ok_(a.get_absolute_url() in resp['Location'])
  1852
+=======
2
Les Orchard Collaborator
lmorchard added a note July 05, 2012

Maybe just the conflict marker chunk needs to be removed?

David Walsh Collaborator
darkwing added a note July 05, 2012

Found, fixed. Sorry about that, not sure how that happened.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
David Walsh Adjust the 'attachments' property of Documents to find all files.
This uses pretty simple logic to scan a Document's HTML for both
MindTouch and kuma file URL patterns, and then uses Q objects to build
a correct query to return the corresponding Attachment objects. If no
file URLs are found in the Document, it'll return an empty Attachment
queryset.

fix bug 770397 - Displaying attachments table under document and tags

fix bug 770563 - Show tags and file attachments regardless of TOC

Bugfix: actually wrap the file queries in Q objects.

Changing file URL method

Removing unnecessary filesize() method from AttachmentRevisions model

Fixing merge conflict
fe75794
luke crouch groovecoder commented on the diff July 06, 2012
apps/wiki/forms.py
@@ -392,3 +394,36 @@ def clean_slug(self):
392 394
         if '/' in self.cleaned_data['slug']:
393 395
             raise forms.ValidationError(SLUG_INVALID)
394 396
         return super(RevisionValidationForm, self).clean_slug()
  397
+
  398
+
  399
+class AttachmentRevisionForm(forms.ModelForm):
  400
+    # Unlike the DocumentForm/RevisionForm split, we have only one
  401
+    # form for file attachments. The handling view will determine if
  402
+    # this is a new revision of an existing file, or the first version
  403
+    # of a new file.
  404
+    #
  405
+    # As a result of this, calling save(commit=True) is off-limits.
  406
+    class Meta:
  407
+        model = AttachmentRevision
  408
+        fields = ('file', 'title', 'description', 'comment')
  409
+
  410
+    def save(self, commit=True):
2
luke crouch Collaborator
groovecoder added a note July 06, 2012

why do we have commit=True default value if it raises an error?

James Bennett Collaborator
ubernostrum added a note July 06, 2012

Mostly because it's the standard API for ModelForm, and toggling the default, or requiring it to be specified explicitly, would break unaware code in ways that doesn't raise the NotImplementedError.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
luke crouch groovecoder commented on the diff July 06, 2012
apps/wiki/views.py
((8 lines not shown))
1481 1482
     # TODO: For now this just grabs and serves the file in the most
1482  
-    # naive way, since that ensures compatibility for the most common
1483  
-    # case where we just want to show the file contents embedded in a
1484  
-    # document.
1485  
-    #
1486  
-    # In the future, this should grow to be multiple views -- one
1487  
-    # legacy view to support document-embedded file URLs, and then
1488  
-    # more full-featured views for showing metadata, revision history,
1489  
-    # uploading new versions, etc.
  1483
+    # naive way. This likely has performance and security implications.
1
luke crouch Collaborator
groovecoder added a note July 06, 2012

we should use some code like https://github.com/mozilla/kuma/blob/master/apps/demos/models.py#L641 to black-list certain mime-types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
luke crouch
Collaborator

We should also add these models to the django admin interface.

luke crouch groovecoder commented on the diff July 06, 2012
((7 lines not shown))
  52
+    # Files.
  53
+    url(r'^files/new/$',
  54
+        'wiki.views.new_attachment',
  55
+        name='wiki.new_attachment'),
  56
+    url(r'^files/(?P<attachment_id>\d+)/$',
  57
+        'wiki.views.attachment_detail',
  58
+        name='wiki.attachment_detail'),
  59
+    url(r'^files/(?P<attachment_id>\d+)/edit/$',
  60
+        'wiki.views.edit_attachment',
  61
+        name='wiki.edit_attachment'),
  62
+    url(r'^files/(?P<attachment_id>\d+)/history/$',
  63
+        'wiki.views.attachment_history',
  64
+        name='wiki.attachment_history'),
  65
+    url(r'^files/(?P<attachment_id>\d+)/(?P<filename>.+)$',
  66
+        'wiki.views.raw_file',
  67
+        name='wiki.raw_file'),
4
luke crouch Collaborator
groovecoder added a note July 06, 2012

should these be in wiki views? I guess then they'd be /en-US/docs/files/* ?

luke crouch Collaborator
groovecoder added a note July 06, 2012

in any case, we should exempt these urls from the url prefixer? (I forget how to do this)

James Bennett Collaborator
ubernostrum added a note July 06, 2012

They need to be top-level since they don't really want to end up under docs/ and subject to the documentation URL hierarchy. Agree that we need to keep the prefixer off 'em, and I don't actually know how to do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
James Bennett
Collaborator

Closing in favor of PR 353.

James Bennett ubernostrum closed this July 06, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jul 05, 2012
David Walsh Adjust the 'attachments' property of Documents to find all files.
This uses pretty simple logic to scan a Document's HTML for both
MindTouch and kuma file URL patterns, and then uses Q objects to build
a correct query to return the corresponding Attachment objects. If no
file URLs are found in the Document, it'll return an empty Attachment
queryset.

fix bug 770397 - Displaying attachments table under document and tags

fix bug 770563 - Show tags and file attachments regardless of TOC

Bugfix: actually wrap the file queries in Q objects.

Changing file URL method

Removing unnecessary filesize() method from AttachmentRevisions model

Fixing merge conflict
fe75794
This page is out of date. Refresh to see the latest.
35  apps/wiki/forms.py
... ...
@@ -1,4 +1,5 @@
1 1
 import json
  2
+import mimetypes
2 3
 import re
3 4
 
4 5
 from django import forms
@@ -17,6 +18,7 @@
17 18
 
18 19
 import wiki.content
19 20
 from wiki.models import (Document, Revision, FirefoxVersion, OperatingSystem,
  21
+                         AttachmentRevision,
20 22
                      FIREFOX_VERSIONS, OPERATING_SYSTEMS, SIGNIFICANCES,
21 23
                      GROUPED_FIREFOX_VERSIONS, GROUPED_OPERATING_SYSTEMS,
22 24
                      CATEGORIES, REVIEW_FLAG_TAGS, RESERVED_SLUGS)
@@ -392,3 +394,36 @@ def clean_slug(self):
392 394
         if '/' in self.cleaned_data['slug']:
393 395
             raise forms.ValidationError(SLUG_INVALID)
394 396
         return super(RevisionValidationForm, self).clean_slug()
  397
+
  398
+
  399
+class AttachmentRevisionForm(forms.ModelForm):
  400
+    # Unlike the DocumentForm/RevisionForm split, we have only one
  401
+    # form for file attachments. The handling view will determine if
  402
+    # this is a new revision of an existing file, or the first version
  403
+    # of a new file.
  404
+    #
  405
+    # As a result of this, calling save(commit=True) is off-limits.
  406
+    class Meta:
  407
+        model = AttachmentRevision
  408
+        fields = ('file', 'title', 'description', 'comment')
  409
+
  410
+    def save(self, commit=True):
  411
+        if commit:
  412
+            raise NotImplementedError
  413
+        rev = super(AttachmentRevisionForm, self).save(commit=False)
  414
+
  415
+        uploaded_file = self.cleaned_data['file']
  416
+        rev.slug = uploaded_file.name
  417
+
  418
+        # guess_type() is usually pretty good at coming up with the
  419
+        # right thing, but if it can't give us an answer, we fall back
  420
+        # to a default of application/octet-stream.
  421
+        #
  422
+        # TODO: we probably want a "manually fix the mime-type"
  423
+        # ability in the admin.
  424
+        mime_type = mimetypes.guess_type(uploaded_file.name)[0]
  425
+        if mime_type is None:
  426
+            mime_type = 'application/octet-stream'
  427
+        rev.mime_type = mime_type
  428
+
  429
+        return rev
34  apps/wiki/models.py
@@ -198,6 +198,7 @@
198 198
 DOCUMENT_LAST_MODIFIED_CACHE_KEY_TMPL = u'kuma:document-last-modified:%s'
199 199
 
200 200
 DEKI_FILE_URL = re.compile(r'@api/deki/files/(?P<file_id>\d+)/=')
  201
+KUMA_FILE_URL = re.compile(r'/files/(?P<file_id>\d+)/.+\..+')
201 202
 
202 203
 
203 204
 class UniqueCollision(Exception):
@@ -805,17 +806,22 @@ def attachments(self):
805 806
         # instead, the page just gets appropriate HTML to embed
806 807
         # whatever type of file it is. So we find them by
807 808
         # regex-searching over the HTML for URLs that match the
808  
-        # MindTouch file URL pattern.
809  
-        #
810  
-        # TODO: Once we settle kuma's file URL pattern and people
811  
-        # start using kuma's file-handling, we'll need to also scan
812  
-        # for the kuma file URL pattern. That'll probably just involve
813  
-        # setting up a couple Q() objects (one for MindTouch file IDs,
814  
-        # one for kuma file IDs) and OR'ing them together to get the
815  
-        # file query.
  809
+        # file URL patterns.
816 810
         mt_files = DEKI_FILE_URL.findall(self.html)
  811
+        kuma_files = KUMA_FILE_URL.findall(self.html)
  812
+        mt_q = kuma_q = params = None
  813
+
817 814
         if mt_files:
818  
-            return Attachment.objects.filter(mindtouch_attachment_id__in=mt_files)
  815
+            # We have at least some MindTouch files.
  816
+            params = models.Q(mindtouch_attachment_id__in=mt_files)
  817
+            if kuma_files:
  818
+                # We also have some kuma files. Use an OR query.
  819
+                params = params | models.Q(id__in=kuma_files)
  820
+        if kuma_files and not params:
  821
+            # We have only kuma files.
  822
+            params = models.Q(id__in=kuma_files)
  823
+        if params:
  824
+            return Attachment.objects.filter(params)
819 825
         # If no files found, return an empty Attachment queryset.
820 826
         return Attachment.objects.none()
821 827
 
@@ -1384,8 +1390,12 @@ class Attachment(models.Model):
1384 1390
 
1385 1391
     @models.permalink
1386 1392
     def get_absolute_url(self):
1387  
-        return ('wiki.attachment_detail', (), {'attachment_id': self.id,
1388  
-                                               'filename': self.current_revision.filename()})
  1393
+        return ('wiki.attachment_detail', (), {'attachment_id': self.id})
  1394
+
  1395
+    @models.permalink
  1396
+    def get_file_url(self):
  1397
+        return ('wiki.raw_file', (), {'attachment_id': self.id,
  1398
+                                      'filename': self.current_revision.filename()})
1389 1399
         
1390 1400
 
1391 1401
 class AttachmentRevision(models.Model):
@@ -1397,8 +1407,6 @@ class AttachmentRevision(models.Model):
1397 1407
 
1398 1408
     file = models.FileField(upload_to=rev_upload_to, max_length=500)
1399 1409
 
1400  
-    # If not supplied, these get auto-filled from the name of the file
1401  
-    # as uploaded.
1402 1410
     title = models.CharField(max_length=255, null=True, db_index=True)
1403 1411
     slug = models.CharField(max_length=255, null=True, db_index=True)
1404 1412
     
1  apps/wiki/templates/wiki/attachment_detail.html
... ...
@@ -0,0 +1 @@
  1
+ 
1  apps/wiki/templates/wiki/attachment_history.html
... ...
@@ -0,0 +1 @@
  1
+ 
58  apps/wiki/templates/wiki/document.html
@@ -170,6 +170,9 @@ <h1 class="page-title">{{ document.title }}</h1>
170 170
         </div>
171 171
       {% endif %}
172 172
 
  173
+      {% set tags = document.tags.all() %}
  174
+      {% set attachments = document.attachments.all() %}
  175
+
173 176
       <div id="wikiArticle" class="page-content boxed">
174 177
         {% if toc_html %}
175 178
         <div id="article-nav">
@@ -180,8 +183,20 @@ <h1 class="page-title">{{ document.title }}</h1>
180 183
             </ol>
181 184
           </div>
182 185
           <ul class="page-anchors">
183  
-            <li class="anchor-tags"><a href="#page-tags">Tags</a></li>
184  
-            <li class="anchor-files"><a href="#page-files">Files</a></li>
  186
+            <li class="anchor-tags">
  187
+              {% if tags|length %}
  188
+                <a href="#page-tags">Tags</a>
  189
+              {% else %}
  190
+                <span title="{{ _('This document has no tags') }}">Tags</span>
  191
+              {% endif %}
  192
+            </li>
  193
+            <li class="anchor-files">
  194
+              {% if attachments|length %}
  195
+                <a href="#page-file">Files</a>
  196
+              {% else %}
  197
+                <span title="{{ _('This document has no files') }}">Files</span>
  198
+              {% endif %}
  199
+            </li>
185 200
           </ul>
186 201
         </div>
187 202
         {% endif %}
@@ -200,10 +215,10 @@ <h1 class="page-title">{{ document.title }}</h1>
200 215
       {% endif %}
201 216
     </div>
202 217
     <section class="page-meta">
203  
-      {% set tags = document.tags.all() %}
  218
+      
204 219
       {% if tags | length %}
205 220
       <section id="page-tags">
206  
-        <h2>{{ _('Tags') }}</h2>
  221
+        <h2>{{ _('Tags') }} ({{ tags | length}})</h2>
207 222
         <div id="deki-page-tags">
208 223
           <ul class="tags">
209 224
             <li>
@@ -215,6 +230,39 @@ <h1 class="page-title">{{ document.title }}</h1>
215 230
         </div>
216 231
       </section>
217 232
       {% endif %}
  233
+
  234
+      {% if attachments | length %}
  235
+        <section id="page-attachments">
  236
+          <h2>{{ _('Attachments') }} ({{ attachments | length}})</h2>
  237
+          <table cellpadding="0" cellspacing="0">
  238
+            <thead>
  239
+              <th>{{ _('File') }}</th>
  240
+              <th>{{ _('Size') }}</th>
  241
+              <th>{{ _('Date') }}</th>
  242
+              <th>{{ _('Attached by') }}</th>
  243
+              <!-- <th>&nbsp;</th>  actions will go here -->
  244
+            </thead>
  245
+            <tbody>
  246
+              {% for attachment in attachments %}
  247
+                <tr>
  248
+                  <td class="attachment-name-cell">
  249
+                    <a href="{{ attachment.get_file_url() }}" title="{{ attachment.get_file_url() }}" target="_blank">{{ attachment.title | safe }}</a>
  250
+                    {% if attachment.current_revision.description %}
  251
+                      <div class="attachment-description">{{ attachment.current_revision.description }}</div>
  252
+                    {% endif %}
  253
+                  </td>
  254
+                  <td>{{ attachment.current_revision.file.size }} bytes</td>
  255
+                  <td>{{ datetimeformat(attachment.current_revision.created, format='datetime') }}</td>
  256
+                  <td><a href="{{ url('devmo.views.profile_view', username=attachment.current_revision.creator) }}">{{ attachment.current_revision.creator }}</a></td>
  257
+                  <!--<td>&nbsp;</td> actions will go here -->
  258
+                </tr>
  259
+              {% endfor %}
  260
+            </tbody>
  261
+          </table>
  262
+        </section>
  263
+      {% endif %}
  264
+
  265
+
218 266
       <section id="doc-contributors">
219 267
         {% trans contributors=user_list(contributors) %}
220 268
           Contributors to this page: {{ contributors }}
@@ -252,4 +300,4 @@ <h1 class="page-title">{{ document.title }}</h1>
252 300
 
253 301
 {% block side %}
254 302
   {% include 'wiki/includes/support_for_selectors.html' %}
255  
-{% endblock %}
  303
+{% endblock %}
1  apps/wiki/templates/wiki/edit_attachment.html
... ...
@@ -0,0 +1 @@
  1
+ 
1  apps/wiki/templates/wiki/new_attachment.html
... ...
@@ -0,0 +1 @@
  1
+ 
106  apps/wiki/tests/test_views.py
@@ -13,6 +13,7 @@
13 13
 from django.contrib.sites.models import Site
14 14
 from django.core.cache import cache
15 15
 from django.core.files.base import ContentFile
  16
+from django.core.files import temp as tempfile
16 17
 from django.db.models import Q
17 18
 
18 19
 import mock
@@ -1846,4 +1847,107 @@ def test_legacy_redirect(self):
1846 1847
                                             'filename': f['filename']})
1847 1848
             resp = self.client.get(mindtouch_url)
1848 1849
             eq_(301, resp.status_code)
1849  
-            ok_(a.get_absolute_url() in resp['Location'])
  1850
+            ok_(a.get_file_url() in resp['Location'])
  1851
+
  1852
+    def test_new_attachment(self):
  1853
+        self.client.login(username='testuser', password='testpass')
  1854
+
  1855
+        # Shamelessly stolen from Django's own file-upload tests.
  1856
+        tdir = tempfile.gettempdir()
  1857
+        file_for_upload = tempfile.NamedTemporaryFile(suffix=".txt", dir=tdir)
  1858
+        file_for_upload.write('I am a test file for upload.')
  1859
+        file_for_upload.seek(0)
  1860
+
  1861
+        post_data = {
  1862
+            'title': 'Test uploaded file',
  1863
+            'description': 'A test file uploaded into kuma.',
  1864
+            'comment': 'Initial upload',
  1865
+            'file': file_for_upload,
  1866
+        }
  1867
+
  1868
+        resp = self.client.post(reverse('wiki.new_attachment'), data=post_data)
  1869
+        eq_(302, resp.status_code)
  1870
+
  1871
+        attachment = Attachment.objects.get(title='Test uploaded file')
  1872
+        eq_(resp['Location'], 'http://testserver%s' % attachment.get_absolute_url())
  1873
+
  1874
+        rev = attachment.current_revision
  1875
+        eq_('testuser', rev.creator.username)
  1876
+        eq_('A test file uploaded into kuma.', rev.description)
  1877
+        eq_('Initial upload', rev.comment)
  1878
+        ok_(rev.is_approved)
  1879
+
  1880
+    def test_edit_attachment(self):
  1881
+        self.client.login(username='testuser', password='testpass')
  1882
+
  1883
+        tdir = tempfile.gettempdir()
  1884
+        file_for_upload = tempfile.NamedTemporaryFile(suffix=".txt", dir=tdir)
  1885
+        file_for_upload.write('I am a test file for editing.')
  1886
+        file_for_upload.seek(0)
  1887
+
  1888
+        post_data = {
  1889
+            'title': 'Test editing file',
  1890
+            'description': 'A test file for editing.',
  1891
+            'comment': 'Initial upload',
  1892
+            'file': file_for_upload,
  1893
+        }
  1894
+
  1895
+        resp = self.client.post(reverse('wiki.new_attachment'), data=post_data)
  1896
+        
  1897
+        tdir = tempfile.gettempdir()
  1898
+        edited_file_for_upload = tempfile.NamedTemporaryFile(suffix=".txt", dir=tdir)
  1899
+        edited_file_for_upload.write('I am a new version of the test file for editing.')
  1900
+        edited_file_for_upload.seek(0)
  1901
+
  1902
+        post_data = {
  1903
+            'title': 'Test editing file',
  1904
+            'description': 'A test file for editing.',
  1905
+            'comment': 'Second revision.',
  1906
+            'file': edited_file_for_upload,
  1907
+        }
  1908
+
  1909
+        attachment = Attachment.objects.get(title='Test editing file')
  1910
+
  1911
+        resp = self.client.post(reverse('wiki.edit_attachment',
  1912
+                                        kwargs={'attachment_id': attachment.id}),
  1913
+                                data=post_data)
  1914
+
  1915
+        eq_(302, resp.status_code)
  1916
+
  1917
+        # Re-fetch because it's been updated.
  1918
+        attachment = Attachment.objects.get(title='Test editing file')
  1919
+        eq_(resp['Location'], 'http://testserver%s' % attachment.get_absolute_url())
  1920
+
  1921
+        eq_(2, attachment.revisions.count())
  1922
+        
  1923
+        rev = attachment.current_revision
  1924
+        eq_('testuser', rev.creator.username)
  1925
+        eq_('Second revision.', rev.comment)
  1926
+        ok_(rev.is_approved)
  1927
+
  1928
+        resp = self.client.get(attachment.get_file_url())
  1929
+        eq_('text/plain', rev.mime_type)
  1930
+        ok_('I am a new version of the test file for editing.' in resp.content)
  1931
+
  1932
+    def test_attachment_detail(self):
  1933
+        self.client.login(username='testuser', password='testpass')
  1934
+
  1935
+        tdir = tempfile.gettempdir()
  1936
+        file_for_upload = tempfile.NamedTemporaryFile(suffix=".txt", dir=tdir)
  1937
+        file_for_upload.write('I am a test file for attachment detail view.')
  1938
+        file_for_upload.seek(0)
  1939
+
  1940
+        post_data = {
  1941
+            'title': 'Test file for viewing',
  1942
+            'description': 'A test file for viewing.',
  1943
+            'comment': 'Initial upload',
  1944
+            'file': file_for_upload,
  1945
+        }
  1946
+
  1947
+        resp = self.client.post(reverse('wiki.new_attachment'), data=post_data)
  1948
+
  1949
+        attachment = Attachment.objects.get(title='Test file for viewing')
  1950
+
  1951
+        resp = self.client.get(reverse('wiki.attachment_detail',
  1952
+                                       kwargs={'attachment_id': attachment.id}))
  1953
+        eq_(200, resp.status_code)
73  apps/wiki/views.py
@@ -50,7 +50,8 @@
50 50
 from wiki.decorators import check_readonly
51 51
 from wiki.events import (EditDocumentEvent, ReviewableRevisionInLocaleEvent,
52 52
                          ApproveRevisionInLocaleEvent)
53  
-from wiki.forms import DocumentForm, RevisionForm, ReviewForm, RevisionValidationForm
  53
+from wiki.forms import (DocumentForm, RevisionForm, ReviewForm, RevisionValidationForm,
  54
+                        AttachmentRevisionForm)
54 55
 from wiki.models import (Document, Revision, HelpfulVote, EditorToolbar,
55 56
                          DocumentTag, ReviewTag, Attachment,
56 57
                          DocumentRenderingInProgress,
@@ -1476,28 +1477,74 @@ def load_documents(request):
1476 1477
                               context_instance=RequestContext(request))
1477 1478
 
1478 1479
 
1479  
-def attachment_detail(request, attachment_id, filename):
1480  
-    """Detail of a file attachment."""
  1480
+def raw_file(request, attachment_id, filename):
  1481
+    """Serve up an attachment's file."""
1481 1482
     # TODO: For now this just grabs and serves the file in the most
1482  
-    # naive way, since that ensures compatibility for the most common
1483  
-    # case where we just want to show the file contents embedded in a
1484  
-    # document.
1485  
-    #
1486  
-    # In the future, this should grow to be multiple views -- one
1487  
-    # legacy view to support document-embedded file URLs, and then
1488  
-    # more full-featured views for showing metadata, revision history,
1489  
-    # uploading new versions, etc.
  1483
+    # naive way. This likely has performance and security implications.
1490 1484
     attachment = get_object_or_404(Attachment, pk=attachment_id)
1491 1485
     if attachment.current_revision is None:
1492 1486
         raise Http404
1493 1487
     rev = attachment.current_revision
1494 1488
     resp = HttpResponse(rev.file.read(), mimetype=rev.mime_type)
1495 1489
     resp["Last-Modified"] = rev.created
1496  
-    resp["Content-Length"] = rev.size
  1490
+    resp["Content-Length"] = rev.file.size
1497 1491
     return resp
1498 1492
 
1499 1493
 
1500 1494
 def mindtouch_file_redirect(request, file_id, filename):
1501 1495
     """Redirect an old MindTouch file URL to a new kuma file URL."""
1502 1496
     attachment = get_object_or_404(Attachment, mindtouch_attachment_id=file_id)
1503  
-    return HttpResponsePermanentRedirect(attachment.get_absolute_url())
  1497
+    return HttpResponsePermanentRedirect(attachment.get_file_url())
  1498
+
  1499
+
  1500
+def attachment_detail(request, attachment_id):
  1501
+    """Detail view of an attachment."""
  1502
+    attachment = get_object_or_404(Attachment, pk=attachment_id)
  1503
+    return jingo.render(request, 'wiki/attachment_detail.html',
  1504
+                        {'attachment': attachment})
  1505
+
  1506
+
  1507
+def attachment_history(request, attachment_id):
  1508
+    """Detail view of an attachment."""
  1509
+    # For now this is just attachment_detail with a different
  1510
+    # template. At some point in the near future, it'd be nice to add
  1511
+    # a few extra bits, like the ability to set an arbitrary revision
  1512
+    # to be current.
  1513
+    attachment = get_object_or_404(Attachment, pk=attachment_id)
  1514
+    return jingo.render(request, 'wiki/attachment_history.html',
  1515
+                        {'attachment': attachment})
  1516
+
  1517
+@login_required
  1518
+def new_attachment(request):
  1519
+    """Create a new Attachment object and populate its initial
  1520
+    revision."""
  1521
+    if request.method == 'POST':
  1522
+        form = AttachmentRevisionForm(data=request.POST, files=request.FILES)
  1523
+        if form.is_valid():
  1524
+            rev = form.save(commit=False)
  1525
+            rev.creator = request.user
  1526
+            attachment = Attachment.objects.create(title=rev.title,
  1527
+                                                   slug=rev.slug)
  1528
+            rev.attachment = attachment
  1529
+            rev.save()
  1530
+            return HttpResponseRedirect(attachment.get_absolute_url())
  1531
+    form = AttachmentRevisionForm()
  1532
+    return jingo.render(request, 'wiki/new_attachment.html',
  1533
+                        {'form': form})
  1534
+
  1535
+
  1536
+@login_required
  1537
+def edit_attachment(request, attachment_id):
  1538
+    attachment = get_object_or_404(Attachment,
  1539
+                                   pk=attachment_id)
  1540
+    if request.method == 'POST':
  1541
+        form = AttachmentRevisionForm(data=request.POST, files=request.FILES)
  1542
+        if form.is_valid():
  1543
+            rev = form.save(commit=False)
  1544
+            rev.creator = request.user
  1545
+            rev.attachment = attachment
  1546
+            rev.save()
  1547
+            return HttpResponseRedirect(attachment.get_absolute_url())
  1548
+    form = AttachmentRevisionForm()
  1549
+    return jingo.render(request, 'wiki/edit_attachment.html',
  1550
+                        {'form': form})
12  media/css/wiki-screen.css
@@ -84,6 +84,13 @@ textarea#id_content { width: 96%; padding: 15px; min-height: 300px; }
84 84
 #page-tags ul.tagit { background: none repeat scroll 0 0 #FFFFFF; border: 1px solid #CBC8B9; padding: 6px 8px; }
85 85
 #page-tags ul.tagit .tagit-label { color: #333333; font-weight: normal; }
86 86
 
  87
+/* page attachments */
  88
+#page-attachments table  { margin: 20px 0 0 0; width: 100%; border: 1px solid #E0E0DC; border-bottom:0; border-right:0; border-collapse: collapse; }
  89
+#page-attachments th, #page-attachments td { padding: 5px; border-bottom: 1px solid #E0E0DC; border-right: 1px solid #E0E0DC; }
  90
+#page-attachments th { font-weight:bold; color: #666; }
  91
+#page-attachments td { font-size: .9em; }
  92
+#page-attachments .attachment-description { font-size: .9em; font-style: italic; }
  93
+#page-attachments .attachment-name-cell { width: 40%; }
87 94
 
88 95
 /*** @Tools @Nav *********/
89 96
 #nav-toolbar { font-size: .785em; text-shadow: 1px 1px 0 rgba(255,255,255,.25); padding: 0; border-top: 1px solid #cbc8b9; border-bottom: 1px solid #f8f8f6; background: #e4e4d9; background: rgba(198,198,175,.35); }
@@ -287,9 +294,12 @@ input#id_title { font-size: 1.857em; width: 80%; padding: .1em 6px; margin: 0 0
287 294
 
288 295
 #article-nav .page-anchors { background: #f1f6fb; margin: 0 0 5px; padding: 6px 18px; border: 1px solid #edf2f7; font-size: .785em; font-style: italic; text-transform: uppercase; }
289 296
 #article-nav .page-anchors li { display: inline; padding: 0; margin-right: 12px; background: none; }
290  
-#article-nav .page-anchors a { padding-left: 16px; }
  297
+#article-nav .page-anchors a, #article-nav .page-anchors span { padding-left: 16px; }
  298
+#article-nav .page-anchors span { cursor: default; }
291 299
 #article-nav .anchor-tags a { background: transparent url("../img/wiki/icons/tag-tiny.png") 0 50% no-repeat; }
  300
+#article-nav .anchor-tags span { background: transparent url("../img/wiki/icons/tag-tiny-disabled.png") 0 50% no-repeat; }
292 301
 #article-nav .anchor-files a { background: transparent url("../img/wiki/icons/attach-tiny.png") 0 50% no-repeat; }
  302
+#article-nav .anchor-files span { background: transparent url("../img/wiki/icons/attach-tiny-disabled.png") 0 50% no-repeat; }
293 303
 
294 304
 /* @IRC @channels *********/
295 305
 .suggestchannels { font-size: .857em; padding: 6px 6px 6px 8px; background-color: #f6f6f6; border: 1px solid #f3f3f3; margin-bottom: 5px; }
BIN  media/img/wiki/icons/attach-tiny-disabled.png
BIN  media/img/wiki/icons/tag-tiny-disabled.png
19  urls.py
@@ -49,9 +49,22 @@
49 49
 
50 50
     #url(r'^', include('dashboards.urls')),
51 51
 
52  
-    # File detail.
53  
-    url(r'^/files/(?P<attachment_id>\d+)/(?P<filename>.+)$',
54  
-        'attachment_detail', name='wiki.attachment_detail'),
  52
+    # Files.
  53
+    url(r'^files/new/$',
  54
+        'wiki.views.new_attachment',
  55
+        name='wiki.new_attachment'),
  56
+    url(r'^files/(?P<attachment_id>\d+)/$',
  57
+        'wiki.views.attachment_detail',
  58
+        name='wiki.attachment_detail'),
  59
+    url(r'^files/(?P<attachment_id>\d+)/edit/$',
  60
+        'wiki.views.edit_attachment',
  61
+        name='wiki.edit_attachment'),
  62
+    url(r'^files/(?P<attachment_id>\d+)/history/$',
  63
+        'wiki.views.attachment_history',
  64
+        name='wiki.attachment_history'),
  65
+    url(r'^files/(?P<attachment_id>\d+)/(?P<filename>.+)$',
  66
+        'wiki.views.raw_file',
  67
+        name='wiki.raw_file'),
55 68
 
56 69
     # Users
57 70
     ('', include('users.urls')),
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.