Merge lp:~dholbach/developer-ubuntu-com/rework-importer into lp:developer-ubuntu-com
- rework-importer
- Merge into stable
Status: | Superseded | ||||||||
---|---|---|---|---|---|---|---|---|---|
Proposed branch: | lp:~dholbach/developer-ubuntu-com/rework-importer | ||||||||
Merge into: | lp:developer-ubuntu-com | ||||||||
Prerequisite: | lp:~developer-ubuntu-com-dev/developer-ubuntu-com/1470715 | ||||||||
Diff against target: |
886 lines (+636/-75) (has conflicts) 12 files modified
TODO (+7/-0) developer_portal/admin.py (+22/-4) developer_portal/management/commands/import-external-docs-branches.py (+42/-56) developer_portal/management/importer/article.py (+126/-0) developer_portal/management/importer/local_branch.py (+132/-0) developer_portal/management/importer/publish.py (+60/-0) developer_portal/management/importer/source.py (+51/-0) developer_portal/migrations/0002_rework_importer.py (+59/-0) developer_portal/migrations/0003_track_imported_articles.py (+24/-0) developer_portal/models.py (+46/-15) developer_portal/settings.py (+20/-0) requirements.txt (+47/-0) Text conflict in developer_portal/admin.py Text conflict in developer_portal/management/commands/import-external-docs-branches.py Text conflict in developer_portal/settings.py Text conflict in requirements.txt |
||||||||
To merge this branch: | bzr merge lp:~dholbach/developer-ubuntu-com/rework-importer | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu App Developer site developers | Pending | ||
Review via email: mp+277722@code.launchpad.net |
Commit message
Description of the change
- 183. By Daniel Holbach
-
make admin interface for ExternalDocsBranch* usable
- 184. By Daniel Holbach
-
first cut of importer reorg
- break up into individual files
- simplify API a bit, make it more easily extendable and testable
- next steps: implement removal of old articles, fix links - 185. By Daniel Holbach
-
we haven't implemented globs yet
- 186. By Daniel Holbach
-
merged from trunk
- 187. By Daniel Holbach
-
add TODO
- 188. By Daniel Holbach
-
merge trunk
- 189. By Daniel Holbach
- 190. By Daniel Holbach
-
some more package updates
- 191. By Daniel Holbach
-
revert reversion upgrade
- 192. By Daniel Holbach
-
update migations locations
- 193. By Daniel Holbach
-
rename branch_origin to origin, add branch_name and add functionatlity so users can specify which branch of a git branch to import form
- 194. By Daniel Holbach
-
make ExternalDocsBra
nch.__str_ _ more revealing - 195. By Daniel Holbach
-
update SnappyLocalBranch c'tor to be inline with the added branch_name field
- 196. By Daniel Holbach
-
make sure we run 'checkout <branch>' in the right directory
- 197. By Daniel Holbach
-
update member name
- 198. By Daniel Holbach
-
fix imports, return published articles
- 199. By Daniel Holbach
-
update TODO
- 200. By Daniel Holbach
-
make pep8 happy, use .format()
- 201. By Daniel Holbach
-
allow us to track which pages were imported from which branch
- 202. By Daniel Holbach
-
make pep8 happy
- 203. By Daniel Holbach
-
first cut at tracking docs-branch/page in a separate table
- 204. By Daniel Holbach
-
fix check if directory-import would import already file-imported articles
- 205. By Daniel Holbach
-
safe datetime of last_import, fix removal condition of old articles
- 206. By Daniel Holbach
-
remove unused import
- 207. By Daniel Holbach
-
merge from lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3
- 208. By Daniel Holbach
-
add check if the last change was made by our script
- 209. By Daniel Holbach
-
merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3 again
- 210. By Daniel Holbach
-
make __str__ representations of ExternalDocsBra
nchImportDirect ive and ExternalDocsBranch more readable - 211. By Daniel Holbach
-
fix syntax error
- 212. By Daniel Holbach
-
every branch gets its own tempdir
- 213. By Daniel Holbach
-
help with paths starting with '/'
- 214. By Daniel Holbach
-
every branch gets its own tempdir
- 215. By Daniel Holbach
-
add fenced_code markdown extension - only change links if they are in our url_map
- 216. By Daniel Holbach
-
allow github style markdown
- 217. By Daniel Holbach
-
remove url_map checks, they are useless
- 218. By Daniel Holbach
-
update TODO
- 219. By Daniel Holbach
-
support local repositories - useful for testing
- 220. By Daniel Holbach
-
add first tests
- 221. By Daniel Holbach
-
add another test
- 222. By Daniel Holbach
-
pep8 fix
- 223. By Daniel Holbach
-
rename tests to be clearer, rewrite tests, so we can reuse a repository we already checked out
- 224. By Daniel Holbach
-
rename local_branch to repo - makes more sense these days
- 225. By Daniel Holbach
-
add test for post_checkout_
command, fix post_checkout_ command logic by chdir()ing correctly, make pep8+pyflakes happy - 226. By Daniel Holbach
-
make create_home_page return home, test page db activities
- 227. By Daniel Holbach
-
check if all imported articles have 'home' as parent
- 228. By Daniel Holbach
-
add comment, pep8
- 229. By Daniel Holbach
-
add function to reset a repo - only useful for tests, where we reuse the same repo to avoid redownloading, add test where only one file is imported and we check the article tree
- 230. By Daniel Holbach
-
filter on publisher_
is_draft= True - 231. By Daniel Holbach
-
clean up after the test
- 232. By Daniel Holbach
-
make index_doc_title available everywhere
- 233. By Daniel Holbach
-
add db_add_empty_page convenience function, add simple articletree test, add test for the import of snappy devel
- 234. By Daniel Holbach
-
make git quiet
- 235. By Daniel Holbach
-
add create_repo convenience function
- 236. By Daniel Holbach
-
use create_repo function, add assertions for SnappyRepo vs Repo instantiation
- 237. By Daniel Holbach
-
add SnappyTestRepo singleton for re-use, rename variables
- 238. By Daniel Holbach
-
add assertions if imported articles have the correct type - drop useless tempdir creation
- 239. By Daniel Holbach
-
function article.get was renamed to article.read ages ago...
- 240. By Daniel Holbach
-
add test for 'current' snappy import (with check for redirects)
- 241. By Daniel Holbach
-
remove superfluous .get() call, add assertion that fetching worked fine
- 242. By Daniel Holbach
-
move snappy import tests into their own class
- 243. By Daniel Holbach
-
reset snappy test repos at the beginning of the test too
- 244. By Daniel Holbach
-
add assertion if snappy repo produces snappy articles
- 245. By Daniel Holbach
-
add reset() SnappyRepo to as well, rename variables and function names to be clearer, produce index_doc first, then add other pages, then refer to them in index_doc - this makes sure the parent (index_doc) is in the db first
- 246. By Daniel Holbach
-
merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3 - resolve conflicts
- 247. By Daniel Holbach
-
<ojii|work> because publishing etc will change a pages PK
<ojii|work> Page().publish() creates a new copy of the page (and deletes the old one) - 248. By Daniel Holbach
-
improve variable usage
- 249. By Daniel Holbach
-
introduce repo.pages to distinguish from .imported_articles
- 250. By Daniel Holbach
-
fix tests
- 251. By Daniel Holbach
-
check for both 'python-api' and 'script'
- 252. By Daniel Holbach
-
break up tests into individual files
- 253. By Daniel Holbach
-
update TODO
- 254. By Daniel Holbach
-
improve create_repo helper function
- 255. By Daniel Holbach
-
allow SourceCode.
_get_branch to use local docs (ie for testing) - 256. By Daniel Holbach
-
first cut at testing local links
- 257. By Daniel Holbach
-
fix simple link rewrite functionality and test
- 258. By Daniel Holbach
-
move md_importer into its own app
- 259. By Daniel Holbach
-
add migration for md_importer
- 260. By Daniel Holbach
- 261. By Daniel Holbach
-
make directory structure clearer
- 262. By Daniel Holbach
-
adapt to new name of management command
- 263. By Daniel Holbach
-
add more readable error message
- 264. By Daniel Holbach
-
add more local test data (imported just the docs from snappy's and snapcraft's git master branches)
- 265. By Daniel Holbach
-
WIP commit:
- move and update TODO file
- define some global values for the importer centrally
- don't sys.exit() when adding pages to the db
- add debug messages
- break _find_parent() into its own function
- mostly make use of local test data
- drop singleton, use separate test classes instead
- add more assertions in tests - 266. By Daniel Holbach
-
remove debug prints, make Repo class variables actual members
- 267. By Daniel Holbach
-
- make pep8 happy, remove unnecessary imports
- use TestLocalBranchImport as base class for almost everything,
reduces a lot of duplication - 268. By Daniel Holbach
-
remove unnecessary code, fix publishing of pages in tests
- 269. By Daniel Holbach
-
fix test_link_rewrite by fixing the URL
- 270. By Daniel Holbach
-
make Article class variables actual members
- 271. By Daniel Holbach
-
add new test to check links in snapcraft import, move link checking function into TestLocalBranch
Import - 272. By Daniel Holbach
-
merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3
- 273. By Daniel Holbach
-
allow empty strings in import directives
- 274. By Daniel Holbach
-
update migration
- 275. By Daniel Holbach
-
check if articles were added below home
- 276. By Daniel Holbach
-
remove unnecessary call to set a page's publisher_is_draft bit
- 277. By Daniel Holbach
-
give article a .publish() method which gives back the page's public_object
- 278. By Daniel Holbach
-
rename home_page to root_page, use cms.test_
utils.testcases .CMSTestCase, make sure we use public_object wherever possible - 279. By Daniel Holbach
-
update tests accordingly
- 280. By Daniel Holbach
-
disregard anchors, make link checking more flexible (ie not only check for intro.md, but also things like docs/intro.md
- 281. By Daniel Holbach
-
make URL work, even if LANG is already part of it
- 282. By Daniel Holbach
-
links look like they're working now
- 283. By Daniel Holbach
-
add test with a broken link, add convenience function is_local_link, modify tests
- 284. By Daniel Holbach
-
import doc fix from https:/
/bugs.launchpad .net/developer- ubuntu- com/+bug/ 1531200 - 285. By Daniel Holbach
-
bug fixed
- 286. By Daniel Holbach
-
stop import if local images are found, add tests
- 287. By Daniel Holbach
-
update TODO
- 288. By Daniel Holbach
-
add test to see if importing the same content twice results in the same number of pages
- 289. By Daniel Holbach
-
when replacing links, only update HTML if things actually change, only publish if page is dirty
- 290. By Daniel Holbach
-
only update page attributes if they actually change, only update the text plugin if the html actually changes, veryify with djangocms_
text_ckeditor. html.clean_ html, add a text plugin to a page, even if html is empty - 291. By Daniel Holbach
-
add test to check if running an import twice will update the articles in question
- 292. By Daniel Holbach
-
update TODO
- 293. By Daniel Holbach
-
add misc tests - forgot to 'bzr add'
- 294. By Daniel Holbach
-
make update-mtemplate work again, simplify it
- 295. By Daniel Holbach
-
update .pot file
- 296. By Daniel Holbach
-
add actual page object to repo.pages
- 297. By Daniel Holbach
-
use UTC for ImportedArticle
.last_import, simplify ImportedArticle cleanup - 298. By Daniel Holbach
-
use repo instead of branch consistently
- 299. By Daniel Holbach
-
break out the process of importing a branch into its own module, add a first simple test for it
- 300. By Daniel Holbach
-
fix clean up of imported articles, add a test
- 301. By Daniel Holbach
-
update TODO
Unmerged revisions
Preview Diff
1 | === added file 'TODO' |
2 | --- TODO 1970-01-01 00:00:00 +0000 |
3 | +++ TODO 2015-12-09 15:41:23 +0000 |
4 | @@ -0,0 +1,7 @@ |
5 | +- import pictures (LP: #1511677) |
6 | +- unpublished docs |
7 | +- add comment at top of RawHTML plugins, so users know not to edit them, |
8 | + currently blocked on LP: #1523925 |
9 | +- when deleting pages, check if last rev was made by python-api |
10 | + |
11 | +- |
12 | |
13 | === modified file 'developer_portal/admin.py' |
14 | --- developer_portal/admin.py 2015-12-04 11:49:10 +0000 |
15 | +++ developer_portal/admin.py 2015-12-09 15:41:23 +0000 |
16 | @@ -4,7 +4,10 @@ |
17 | from reversion.admin import VersionAdmin |
18 | |
19 | from cms.extensions import TitleExtensionAdmin |
20 | -from .models import ExternalDocsBranch, SEOExtension |
21 | +from .models import ( |
22 | + ExternalDocsBranch, ExternalDocsBranchImportDirective, |
23 | + ImportedArticle, SEOExtension |
24 | +) |
25 | from django.core.management import call_command |
26 | |
27 | __all__ = ( |
28 | @@ -13,7 +16,12 @@ |
29 | |
30 | def import_selected_external_docs_branches(modeladmin, request, queryset): |
31 | for branch in queryset: |
32 | +<<<<<<< TREE |
33 | call_command('import-external-docs-branches', branch.docs_namespace) |
34 | +======= |
35 | + branches.append(branch.origin) |
36 | + call_command('import-external-docs-branches', *branches) |
37 | +>>>>>>> MERGE-SOURCE |
38 | import_selected_external_docs_branches.short_description = \ |
39 | "Import selected branches" |
40 | |
41 | @@ -35,12 +43,22 @@ |
42 | admin.site.register(Version, VersionAdmin) |
43 | |
44 | |
45 | +@admin.register(ExternalDocsBranch) |
46 | class ExternalDocsBranchAdmin(admin.ModelAdmin): |
47 | - list_display = ('lp_origin', 'docs_namespace') |
48 | - list_filter = ('lp_origin', 'docs_namespace') |
49 | + list_display = ('origin', 'post_checkout_command', 'branch_name',) |
50 | + list_filter = ('origin', 'post_checkout_command', 'branch_name',) |
51 | actions = [import_selected_external_docs_branches] |
52 | |
53 | -admin.site.register(ExternalDocsBranch, ExternalDocsBranchAdmin) |
54 | + |
55 | +@admin.register(ExternalDocsBranchImportDirective) |
56 | +class ExternalDocsBranchImportDirectiveAdmin(admin.ModelAdmin): |
57 | + pass |
58 | + |
59 | + |
60 | +@admin.register(ImportedArticle) |
61 | +class ImportedArticleAdmin(admin.ModelAdmin): |
62 | + pass |
63 | + |
64 | |
65 | class SEOExtensionAdmin(TitleExtensionAdmin): |
66 | pass |
67 | |
68 | === modified file 'developer_portal/management/commands/import-external-docs-branches.py' |
69 | --- developer_portal/management/commands/import-external-docs-branches.py 2015-12-04 11:49:10 +0000 |
70 | +++ developer_portal/management/commands/import-external-docs-branches.py 2015-12-09 15:41:23 +0000 |
71 | @@ -1,23 +1,14 @@ |
72 | from django.core.management.base import BaseCommand |
73 | from django.core.management import call_command |
74 | -from django.db import transaction |
75 | - |
76 | -from cms.api import create_page, add_plugin |
77 | -from cms.models import Page, Title |
78 | -from cms.utils import page_resolver |
79 | - |
80 | -from bs4 import BeautifulSoup |
81 | -import codecs |
82 | -import glob |
83 | + |
84 | +from ..importer.local_branch import LocalBranch, SnappyLocalBranch |
85 | + |
86 | +import datetime |
87 | import logging |
88 | -import markdown |
89 | -import os |
90 | -import re |
91 | import shutil |
92 | -import subprocess |
93 | -import sys |
94 | import tempfile |
95 | |
96 | +<<<<<<< TREE |
97 | from developer_portal.models import ExternalDocsBranch |
98 | |
99 | DOCS_DIRNAME = 'docs' |
100 | @@ -295,6 +286,13 @@ |
101 | placeholder = page.placeholders.get() |
102 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) |
103 | return page |
104 | +======= |
105 | +from developer_portal.models import ( |
106 | + ExternalDocsBranch, |
107 | + ExternalDocsBranchImportDirective, |
108 | + ImportedArticle, |
109 | +) |
110 | +>>>>>>> MERGE-SOURCE |
111 | |
112 | |
113 | def import_branches(selection): |
114 | @@ -303,51 +301,39 @@ |
115 | 'ExternalDocsBranch table yet.') |
116 | return |
117 | tempdir = tempfile.mkdtemp() |
118 | - db_actions = DBActions() |
119 | for branch in ExternalDocsBranch.objects.filter( |
120 | - docs_namespace__regex=selection): |
121 | - checkout_location = os.path.join( |
122 | - tempdir, os.path.basename(branch.docs_namespace)) |
123 | - sourcecode = SourceCode(branch.lp_origin, checkout_location) |
124 | - if sourcecode.get() != 0: |
125 | - logging.error( |
126 | - 'Could not check out branch "%s".' % branch.lp_origin) |
127 | - if os.path.exists(checkout_location): |
128 | - shutil.rmtree(checkout_location) |
129 | + origin__regex=selection, active=True): |
130 | + url = branch.origin |
131 | + if url.startswith('lp:snappy') or \ |
132 | + 'snappy' in url.split(':')[1].split('.git')[0].split('/'): |
133 | + branch_class = SnappyLocalBranch |
134 | + else: |
135 | + branch_class = LocalBranch |
136 | + local_branch = branch_class(tempdir, branch.origin, branch.branch_name, |
137 | + branch.post_checkout_command) |
138 | + if local_branch.get() != 0: |
139 | break |
140 | - if branch.lp_origin.startswith('lp:snappy') or \ |
141 | - 'snappy' in branch.lp_origin.split(':')[1].split('.git')[0].split('/'): |
142 | - local_branch = SnappyLocalBranch(checkout_location, branch, |
143 | - db_actions) |
144 | - else: |
145 | - local_branch = LocalBranch(checkout_location, branch, db_actions) |
146 | - local_branch.import_markdown() |
147 | - local_branch.publish() |
148 | - local_branch.remove_old_pages() |
149 | + for directive in ExternalDocsBranchImportDirective.objects.filter( |
150 | + external_docs_branch=branch): |
151 | + local_branch.add_directive(directive.import_from, |
152 | + directive.write_to) |
153 | + local_branch.execute_import_directives() |
154 | + imported_articles = local_branch.publish() |
155 | + for imported_article in imported_articles: |
156 | + ImportedArticle.objects.get_or_create( |
157 | + branch=branch, |
158 | + page=imported_article.page, |
159 | + last_import=datetime.datetime.now()) |
160 | + |
161 | + # The import is done, now let's clean up. |
162 | + for old_article in ImportedArticle.objects.filter(branch=branch): |
163 | + if old_article.page not in [a.page for a in imported_articles]: |
164 | + # XXX: check if last revision was made by 'python-api'? |
165 | + old_article.page.delete() |
166 | shutil.rmtree(tempdir) |
167 | - db_actions.run() |
168 | - |
169 | - |
170 | -class SourceCode(): |
171 | - def __init__(self, branch_origin, checkout_location): |
172 | - self.branch_origin = branch_origin |
173 | - self.checkout_location = checkout_location |
174 | - |
175 | - def get(self): |
176 | - if self.branch_origin.startswith('lp:') and \ |
177 | - os.path.exists('/usr/bin/bzr'): |
178 | - return subprocess.call([ |
179 | - 'bzr', 'checkout', '--lightweight', self.branch_origin, |
180 | - self.checkout_location]) |
181 | - if self.branch_origin.startswith('https://github.com') and \ |
182 | - self.branch_origin.endswith('.git') and \ |
183 | - os.path.exists('/usr/bin/git'): |
184 | - return subprocess.call([ |
185 | - 'git', 'clone', '-q', self.branch_origin, |
186 | - self.checkout_location]) |
187 | - logging.error( |
188 | - 'Branch format "{}" not understood.'.format(self.branch_origin)) |
189 | - return 1 |
190 | + |
191 | + # https://stackoverflow.com/questions/33284171/ |
192 | + call_command('cms', 'fix-tree') |
193 | |
194 | |
195 | class Command(BaseCommand): |
196 | |
197 | === added directory 'developer_portal/management/importer' |
198 | === added file 'developer_portal/management/importer/__init__.py' |
199 | === added file 'developer_portal/management/importer/article.py' |
200 | --- developer_portal/management/importer/article.py 1970-01-01 00:00:00 +0000 |
201 | +++ developer_portal/management/importer/article.py 2015-12-09 15:41:23 +0000 |
202 | @@ -0,0 +1,126 @@ |
203 | +from bs4 import BeautifulSoup |
204 | +import codecs |
205 | +import logging |
206 | +import markdown |
207 | +import os |
208 | +import re |
209 | + |
210 | +from .publish import get_or_create_page, slugify |
211 | + |
212 | + |
213 | +class Article: |
214 | + html = None |
215 | + page = None |
216 | + title = "" |
217 | + |
218 | + def __init__(self, fn, write_to): |
219 | + self.fn = fn |
220 | + self.write_to = slugify(self.fn) |
221 | + self.full_url = write_to |
222 | + self.slug = os.path.basename(self.full_url) |
223 | + |
224 | + def read(self): |
225 | + with codecs.open(self.fn, 'r', encoding='utf-8') as f: |
226 | + if self.fn.endswith('.md'): |
227 | + self.html = markdown.markdown( |
228 | + f.read(), |
229 | + output_format='html5', |
230 | + extensions=['markdown.extensions.tables']) |
231 | + elif self.fn.endswith('.html'): |
232 | + self.html = f.read() |
233 | + else: |
234 | + logging.error("Don't know how to interpret '{}'.".format( |
235 | + self.fn)) |
236 | + return False |
237 | + self.title = self._read_title() |
238 | + self._remove_body_and_html_tags() |
239 | + self._use_developer_site_style() |
240 | + return True |
241 | + |
242 | + def _read_title(self): |
243 | + soup = BeautifulSoup(self.html, 'html5lib') |
244 | + if soup.title: |
245 | + return soup.title.text |
246 | + if soup.h1: |
247 | + return soup.h1.text |
248 | + return slugify(self.fn).replace('-', ' ').title() |
249 | + |
250 | + def _remove_body_and_html_tags(self): |
251 | + self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, |
252 | + flags=re.MULTILINE) |
253 | + self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, |
254 | + flags=re.MULTILINE) |
255 | + |
256 | + def _use_developer_site_style(self): |
257 | + begin = (u"<div class=\"row no-border\">" |
258 | + "\n<div class=\"eight-col\">\n") |
259 | + end = u"</div>\n</div>" |
260 | + self.html = begin + self.html + end |
261 | + self.html = self.html.replace( |
262 | + "<pre><code>", |
263 | + "</div><div class=\"twelve-col\"><pre><code>") |
264 | + self.html = self.html.replace( |
265 | + "</code></pre>", |
266 | + "</code></pre></div><div class=\"eight-col\">") |
267 | + |
268 | + def replace_links(self, titles, url_map): |
269 | + for title in titles: |
270 | + local_md_fn = os.path.basename(title) |
271 | + url = u'/'+url_map[title] |
272 | + # Replace links of the form <a href="/path/somefile.md"> first |
273 | + href = u"<a href=\"{}\">".format(url) |
274 | + md_href = u"<a href=\"{}\">".format(local_md_fn) |
275 | + self.html = self.html.replace(md_href, href) |
276 | + |
277 | + # Now we can replace free-standing "somefile.md" references in |
278 | + # the HTML |
279 | + link = href + u"{}</a>".format(titles[title]) |
280 | + self.html = self.html.replace(local_md_fn, link) |
281 | + |
282 | + def publish(self): |
283 | + '''Publishes pages in their branch alias namespace.''' |
284 | + page = get_or_create_page( |
285 | + title=self.title, full_url=self.full_url, menu_title=self.title, |
286 | + html=self.html) |
287 | + self.page = page |
288 | + self.page.publish('en') |
289 | + |
290 | + |
291 | +class SnappyArticle(Article): |
292 | + release_alias = None |
293 | + |
294 | + def get(self): |
295 | + if not Article.get(self): |
296 | + return False |
297 | + self.release_alias = re.findall(r'snappy/guides/(\S+?)/\S+?', |
298 | + self.full_url)[0] |
299 | + self._make_snappy_mods() |
300 | + return True |
301 | + |
302 | + def _make_snappy_mods(self): |
303 | + # Make sure the reader knows which documentation she is browsing |
304 | + if self.release_alias != 'current': |
305 | + before = (u"<div class=\"row no-border\">\n" |
306 | + "<div class=\"eight-col\">\n") |
307 | + after = (u"<div class=\"row no-border\">\n" |
308 | + "<div class=\"box pull-three three-col\">" |
309 | + "<p>You are browsing the Snappy <code>%s</code> " |
310 | + "documentation.</p>" |
311 | + "<p><a href=\"/snappy/guides/current/%s\">" |
312 | + "Back to the latest stable release ›" |
313 | + "</a></p></div>\n" |
314 | + "<div class=\"eight-col\">\n") % (self.release_alias, |
315 | + self.slug, ) |
316 | + self.html = self.html.replace(before, after) |
317 | + |
318 | + def publish(self): |
319 | + if self.release_alias == "current": |
320 | + # Add a guides/<page> redirect to guides/current/<page> |
321 | + page = get_or_create_page( |
322 | + title=self.title, |
323 | + full_url=self.full_url.replace('/current', ''), |
324 | + redirect="/snappy/guides/current/{}".format(self.slug)) |
325 | + page.publish('en') |
326 | + else: |
327 | + self.title += " (%s)" % (self.release_alias,) |
328 | + Article.publish(self) |
329 | |
330 | === added file 'developer_portal/management/importer/local_branch.py' |
331 | --- developer_portal/management/importer/local_branch.py 1970-01-01 00:00:00 +0000 |
332 | +++ developer_portal/management/importer/local_branch.py 2015-12-09 15:41:23 +0000 |
333 | @@ -0,0 +1,132 @@ |
334 | +from .article import Article, SnappyArticle |
335 | +from .publish import get_or_create_page, slugify |
336 | +from .source import SourceCode |
337 | + |
338 | +import glob |
339 | +import logging |
340 | +import os |
341 | +import shutil |
342 | + |
343 | + |
344 | +class LocalBranch: |
345 | + titles = {} |
346 | + url_map = {} |
347 | + index_doc_title = None |
348 | + index_doc = None |
349 | + release_alias = None |
350 | + |
351 | + def __init__(self, tempdir, origin, branch_name, post_checkout_command): |
352 | + self.origin = origin |
353 | + self.branch_name = branch_name |
354 | + self.post_checkout_command = post_checkout_command |
355 | + self.checkout_location = os.path.join( |
356 | + tempdir, |
357 | + os.path.basename(self.origin.replace('.git', ''))) |
358 | + self.article_class = Article |
359 | + self.directives = [] |
360 | + self.imported_articles = [] |
361 | + |
362 | + def get(self): |
363 | + sourcecode = SourceCode(self.origin, self.checkout_location, |
364 | + self.branch_name, self.post_checkout_command) |
365 | + if sourcecode.get() != 0: |
366 | + logging.error( |
367 | + 'Could not check out branch "{}".'.format(self.origin)) |
368 | + shutil.rmtree(self.checkout_location) |
369 | + return 1 |
370 | + return 0 |
371 | + |
372 | + def add_directive(self, import_from, write_to): |
373 | + self.directives += [ |
374 | + { |
375 | + 'import_from': os.path.join(self.checkout_location, |
376 | + import_from), |
377 | + 'write_to': write_to |
378 | + } |
379 | + ] |
380 | + |
381 | + def execute_import_directives(self): |
382 | + import_list = [] |
383 | + # Import single files first |
384 | + for directive in [d for d in self.directives |
385 | + if os.path.isfile(d['import_from'])]: |
386 | + import_list += [ |
387 | + (directive['import_from'], directive['write_to']) |
388 | + ] |
389 | + # Import directories next |
390 | + for directive in [d for d in self.directives |
391 | + if os.path.isdir(d['import_from'])]: |
392 | + for fn in glob.glob('{}/*'.format(directive['import_from'])): |
393 | + if fn not in [a[0] for a in import_list]: |
394 | + import_list += [ |
395 | + (fn, os.path.join(directive['write_to'], slugify(fn))) |
396 | + ] |
397 | + # If we import into a namespace and don't have an index doc, |
398 | + # we need to write one. |
399 | + if directive['write_to'] not in [x[1] for x in import_list]: |
400 | + self.index_doc = directive['write_to'] |
401 | + # The actual import |
402 | + for entry in import_list: |
403 | + article = self._read_article(entry[0], entry[1]) |
404 | + if article: |
405 | + self.imported_articles += [article] |
406 | + self.titles[article.fn] = article.title |
407 | + self.url_map[article.fn] = article.full_url |
408 | + for article in self.imported_articles: |
409 | + article.replace_links(self.titles, self.url_map) |
410 | + if self.index_doc: |
411 | + self._create_fake_index_docs() |
412 | + |
413 | + def _read_article(self, fn, write_to): |
414 | + article = self.article_class(fn, write_to) |
415 | + if article.read(): |
416 | + return article |
417 | + return None |
418 | + |
419 | + def publish(self): |
420 | + for article in self.imported_articles: |
421 | + article.publish() |
422 | + return self.imported_articles |
423 | + |
424 | + def _create_fake_index_docs(self): |
425 | + '''Creates a fake index page at the top of the branches |
426 | + docs namespace.''' |
427 | + |
428 | + if self.index_doc.endswith('current'): |
429 | + redirect = '/snappy/guides' |
430 | + else: |
431 | + redirect = None |
432 | + list_pages = '' |
433 | + for article in [a for a |
434 | + in self.imported_articles |
435 | + if a.full_url.startswith(self.index_doc)]: |
436 | + list_pages += '<li><a href=\"{}\">{}</a></li>'.format( |
437 | + os.path.basename(article.full_url), article.title) |
438 | + landing = ( |
439 | + u'<div class=\"row\"><div class=\"eight-col\">\n' |
440 | + '<p>This section contains documentation for the ' |
441 | + '<code>{}</code> Snappy branch.</p>' |
442 | + '<p><ul class=\"list-ubuntu\">{}</ul></p>\n' |
443 | + '<p>Auto-imported from <a ' |
444 | + 'href=\"https://github.com/ubuntu-core/snappy\">%s</a>.</p>\n' |
445 | + '</div></div>'.format(self.release_alias, list_pages, |
446 | + self.origin)) |
447 | + page = get_or_create_page( |
448 | + title=self.index_doc_title, full_url=self.index_doc, |
449 | + in_navigation=False, redirect=redirect, html=landing, |
450 | + menu_title=None) |
451 | + page.publish('en') |
452 | + |
453 | + |
454 | +class SnappyLocalBranch(LocalBranch): |
455 | + def __init__(self, tempdir, origin, branch_name, post_checkout_command): |
456 | + LocalBranch.__init__(self, tempdir, origin, branch_name, |
457 | + post_checkout_command) |
458 | + self.article_class = SnappyArticle |
459 | + self.index_doc_title = 'Snappy documentation' |
460 | + |
461 | + def _create_fake_index_docs(self): |
462 | + self.release_alias = os.path.basename(self.index_doc) |
463 | + if not self.index_doc.endswith('current'): |
464 | + self.index_doc_title += ' ({})'.format(self.release_alias) |
465 | + LocalBranch._create_fake_index_docs(self) |
466 | |
467 | === added file 'developer_portal/management/importer/publish.py' |
468 | --- developer_portal/management/importer/publish.py 1970-01-01 00:00:00 +0000 |
469 | +++ developer_portal/management/importer/publish.py 2015-12-09 15:41:23 +0000 |
470 | @@ -0,0 +1,60 @@ |
471 | +from cms.api import create_page, add_plugin |
472 | +from cms.models import Title |
473 | + |
474 | +import logging |
475 | +import os |
476 | +import sys |
477 | + |
478 | +# XXX: use this once we RawHTML plugins don't strip comments (LP: #1523925) |
479 | +START_TEXT = """ |
480 | +<!-- |
481 | +branch id: {} |
482 | + |
483 | +THIS PAGE IS AUTOMATICALLY UPDATED. |
484 | +DON'T EDIT IT - CHANGES WILL BE OVERWRITTEN. |
485 | +--> |
486 | +""" |
487 | + |
488 | + |
489 | +def slugify(filename): |
490 | + return os.path.basename(filename).replace('.md', '').replace('.html', '') |
491 | + |
492 | + |
493 | +def get_or_create_page(title, full_url, menu_title=None, |
494 | + in_navigation=True, redirect=None, html=None): |
495 | + # First check if pages already exist. |
496 | + pages = Title.objects.select_related('page').filter(path__regex=full_url) |
497 | + if pages: |
498 | + page = pages[0].page |
499 | + page.title = title |
500 | + page.publisher_is_draft = True |
501 | + page.menu_title = menu_title |
502 | + page.in_navigation = in_navigation |
503 | + page.redirect = redirect |
504 | + if html: |
505 | + # We create the page, so we know there's just one placeholder |
506 | + placeholder = page.placeholders.all()[0] |
507 | + if placeholder.get_plugins(): |
508 | + plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] |
509 | + plugin.body = html |
510 | + plugin.save() |
511 | + else: |
512 | + add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) |
513 | + else: |
514 | + parent_pages = Title.objects.select_related('page').filter( |
515 | + path__regex=os.path.dirname(full_url)) |
516 | + if not parent_pages: |
517 | + logging.error('Parent {} not found.'.format( |
518 | + os.path.dirname(full_url))) |
519 | + sys.exit(1) |
520 | + parent = parent_pages[0].page |
521 | + |
522 | + slug = os.path.basename(full_url) |
523 | + page = create_page( |
524 | + title, "default.html", "en", slug=slug, parent=parent, |
525 | + menu_title=menu_title, in_navigation=in_navigation, |
526 | + position="last-child", redirect=redirect) |
527 | + if html: |
528 | + placeholder = page.placeholders.get() |
529 | + add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) |
530 | + return page |
531 | |
532 | === added file 'developer_portal/management/importer/source.py' |
533 | --- developer_portal/management/importer/source.py 1970-01-01 00:00:00 +0000 |
534 | +++ developer_portal/management/importer/source.py 2015-12-09 15:41:23 +0000 |
535 | @@ -0,0 +1,51 @@ |
536 | +import logging |
537 | +import os |
538 | +import subprocess |
539 | + |
540 | + |
541 | +class SourceCode(): |
542 | + def __init__(self, origin, checkout_location, branch_name, |
543 | + post_checkout_command): |
544 | + self.origin = origin |
545 | + self.checkout_location = checkout_location |
546 | + self.branch_name = branch_name |
547 | + self.post_checkout_command = post_checkout_command |
548 | + |
549 | + def get(self): |
550 | + res = self._get_branch() |
551 | + if res == 0 and self.post_checkout_command: |
552 | + res = self._post_checkout() |
553 | + return res |
554 | + return res |
555 | + |
556 | + def _get_branch(self): |
557 | + if self.origin.startswith('lp:') and \ |
558 | + os.path.exists('/usr/bin/bzr'): |
559 | + return subprocess.call([ |
560 | + 'bzr', 'checkout', '--lightweight', self.origin, |
561 | + self.checkout_location]) |
562 | + if self.origin.startswith('https://github.com') and \ |
563 | + self.origin.endswith('.git') and \ |
564 | + os.path.exists('/usr/bin/git'): |
565 | + retcode = subprocess.call([ |
566 | + 'git', 'clone', '-q', self.origin, |
567 | + self.checkout_location]) |
568 | + if retcode == 0 and self.branch_name: |
569 | + pwd = os.getcwd() |
570 | + os.chdir(self.checkout_location) |
571 | + retcode = subprocess.call(['git', 'checkout', |
572 | + self.branch_name]) |
573 | + os.chdir(pwd) |
574 | + return retcode |
575 | + logging.error( |
576 | + 'Branch format "{}" not understood.'.format(self.origin)) |
577 | + return 1 |
578 | + |
579 | + def _post_checkout(self): |
580 | + process = subprocess.Popen(self.post_checkout_command.split(), |
581 | + stdout=subprocess.PIPE) |
582 | + (out, err) = process.communicate() |
583 | + retcode = process.wait() |
584 | + if retcode != 0: |
585 | + logging.error(out) |
586 | + return retcode |
587 | |
588 | === added file 'developer_portal/migrations/0002_rework_importer.py' |
589 | --- developer_portal/migrations/0002_rework_importer.py 1970-01-01 00:00:00 +0000 |
590 | +++ developer_portal/migrations/0002_rework_importer.py 2015-12-09 15:41:23 +0000 |
591 | @@ -0,0 +1,59 @@ |
592 | +# -*- coding: utf-8 -*- |
593 | +from __future__ import unicode_literals |
594 | + |
595 | +from django.db import migrations, models |
596 | + |
597 | + |
598 | +class Migration(migrations.Migration): |
599 | + |
600 | + dependencies = [ |
601 | + ('developer_portal', '0001_initial'), |
602 | + ] |
603 | + |
604 | + operations = [ |
605 | + migrations.CreateModel( |
606 | + name='ExternalDocsBranchImportDirective', |
607 | + fields=[ |
608 | + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
609 | + ('import_from', models.CharField(help_text='File or directory to import from the branch. Ie: "docs/intro.md" (file) or "docs" (complete directory), etc.', max_length=150)), |
610 | + ('write_to', models.CharField(help_text='Article URL (for a specific file) or article namespace for a directory or a set of files.', max_length=150)), |
611 | + ], |
612 | + ), |
613 | + migrations.AlterModelOptions( |
614 | + name='externaldocsbranch', |
615 | + options={'verbose_name': 'external docs branch', 'verbose_name_plural': 'external docs branches'}, |
616 | + ), |
617 | + migrations.RenameField( |
618 | + model_name='externaldocsbranch', |
619 | + old_name='lp_origin', |
620 | + new_name='origin', |
621 | + ), |
622 | + migrations.RemoveField( |
623 | + model_name='externaldocsbranch', |
624 | + name='docs_namespace', |
625 | + ), |
626 | + migrations.RemoveField( |
627 | + model_name='externaldocsbranch', |
628 | + name='index_doc', |
629 | + ), |
630 | + migrations.AddField( |
631 | + model_name='externaldocsbranch', |
632 | + name='active', |
633 | + field=models.BooleanField(default=True), |
634 | + ), |
635 | + migrations.AddField( |
636 | + model_name='externaldocsbranch', |
637 | + name='branch_name', |
638 | + field=models.CharField(help_text='For use with git branches, ie: "master" or "15.04" or "1.x".', max_length=200, blank=True), |
639 | + ), |
640 | + migrations.AddField( |
641 | + model_name='externaldocsbranch', |
642 | + name='post_checkout_command', |
643 | + field=models.CharField(help_text='Command to run after checkout of the branch.', max_length=100, blank=True), |
644 | + ), |
645 | + migrations.AddField( |
646 | + model_name='externaldocsbranchimportdirective', |
647 | + name='external_docs_branch', |
648 | + field=models.ForeignKey(to='developer_portal.ExternalDocsBranch'), |
649 | + ), |
650 | + ] |
651 | |
652 | === added file 'developer_portal/migrations/0003_track_imported_articles.py' |
653 | --- developer_portal/migrations/0003_track_imported_articles.py 1970-01-01 00:00:00 +0000 |
654 | +++ developer_portal/migrations/0003_track_imported_articles.py 2015-12-09 15:41:23 +0000 |
655 | @@ -0,0 +1,24 @@ |
656 | +# -*- coding: utf-8 -*- |
657 | +from __future__ import unicode_literals |
658 | + |
659 | +from django.db import migrations, models |
660 | + |
661 | + |
662 | +class Migration(migrations.Migration): |
663 | + |
664 | + dependencies = [ |
665 | + ('cms', '0013_urlconfrevision'), |
666 | + ('developer_portal', '0002_rework_importer'), |
667 | + ] |
668 | + |
669 | + operations = [ |
670 | + migrations.CreateModel( |
671 | + name='ImportedArticle', |
672 | + fields=[ |
673 | + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
674 | + ('last_import', models.DateTimeField(help_text='Datetime of last import.', verbose_name='Datetime')), |
675 | + ('branch', models.ForeignKey(to='developer_portal.ExternalDocsBranch')), |
676 | + ('page', models.ForeignKey(to='cms.Page')), |
677 | + ], |
678 | + ), |
679 | + ] |
680 | |
681 | === modified file 'developer_portal/models.py' |
682 | --- developer_portal/models.py 2015-11-02 16:47:26 +0000 |
683 | +++ developer_portal/models.py 2015-12-09 15:41:23 +0000 |
684 | @@ -1,9 +1,9 @@ |
685 | from django.db import models |
686 | from django.utils.translation import ugettext_lazy as _ |
687 | |
688 | -from cms.models import CMSPlugin |
689 | from cms.extensions import TitleExtension |
690 | from cms.extensions.extension_pool import extension_pool |
691 | +from cms.models import Page |
692 | from djangocms_text_ckeditor.html import extract_images |
693 | from djangocms_text_ckeditor.models import AbstractText |
694 | |
695 | @@ -21,23 +21,54 @@ |
696 | |
697 | |
698 | class ExternalDocsBranch(models.Model): |
699 | - # We originally assumed that branches would also live in LP, |
700 | - # well, we were wrong, but let's keep the name around. It's |
701 | - # no use having a schema/data migration just for this. |
702 | - lp_origin = models.CharField( |
703 | + origin = models.CharField( |
704 | max_length=200, |
705 | help_text=_('External branch location, ie: lp:snappy/15.04 or ' |
706 | 'https://github.com/ubuntu-core/snappy.git')) |
707 | - docs_namespace = models.CharField( |
708 | - max_length=120, |
709 | - help_text=_('Path alias we want to use for the docs, ' |
710 | - 'ie "snappy/guides/15.04" or ' |
711 | - '"snappy/guides/latest", etc.')) |
712 | - index_doc = models.CharField( |
713 | - max_length=120, |
714 | - help_text=_('File name of doc to be used as index document, ' |
715 | - 'ie "intro.md"'), |
716 | - blank=True) |
717 | + branch_name = models.CharField( |
718 | + max_length=200, |
719 | + help_text=_('For use with git branches, ie: "master" or "15.04" ' |
720 | + 'or "1.x".'), |
721 | + blank=True) |
722 | + post_checkout_command = models.CharField( |
723 | + max_length=100, |
724 | + help_text=_('Command to run after checkout of the branch.'), |
725 | + blank=True) |
726 | + active = models.BooleanField(default=True) |
727 | + |
728 | + def __str__(self): |
729 | + if self.branch_name: |
730 | + return "{} {}".format(self.origin, self.branch_name) |
731 | + return "{}".format(self.origin) |
732 | + |
733 | + class Meta: |
734 | + verbose_name = "external docs branch" |
735 | + verbose_name_plural = "external docs branches" |
736 | + |
737 | + |
738 | +class ExternalDocsBranchImportDirective(models.Model): |
739 | + external_docs_branch = models.ForeignKey(ExternalDocsBranch) |
740 | + import_from = models.CharField( |
741 | + max_length=150, |
742 | + help_text=_('File or directory to import from the branch. ' |
743 | + 'Ie: "docs/intro.md" (file) or ' |
744 | + '"docs" (complete directory), etc.')) |
745 | + write_to = models.CharField( |
746 | + max_length=150, |
747 | + help_text=_('Article URL (for a specific file) or article namespace ' |
748 | + 'for a directory or a set of files.')) |
749 | + |
750 | + def __str__(self): |
751 | + return "{} -- {}".format(self.external_docs_branch.origin, |
752 | + self.import_from) |
753 | + |
754 | + |
755 | +class ImportedArticle(models.Model): |
756 | + page = models.ForeignKey(Page) |
757 | + branch = models.ForeignKey(ExternalDocsBranch) |
758 | + last_import = models.DateTimeField( |
759 | + _('Datetime'), help_text=_('Datetime of last import.')) |
760 | + |
761 | |
762 | class SEOExtension(TitleExtension): |
763 | keywords = models.CharField(max_length=256) |
764 | |
765 | === modified file 'developer_portal/settings.py' |
766 | --- developer_portal/settings.py 2015-12-04 16:09:27 +0000 |
767 | +++ developer_portal/settings.py 2015-12-09 15:41:23 +0000 |
768 | @@ -318,6 +318,26 @@ |
769 | #'PAGINATE_BY': 10, |
770 | } |
771 | |
772 | +<<<<<<< TREE |
773 | +======= |
774 | +MIGRATION_MODULES = { |
775 | + 'cms': 'cms.migrations', |
776 | + 'cmsplugin_zinnia': 'cmsplugin_zinnia.migrations', |
777 | + 'djangocms_link': 'djangocms_link.migrations', |
778 | + 'djangocms_picture': 'djangocms_picture.migrations', |
779 | + 'djangocms_snippet': 'djangocms_snippet.migrations', |
780 | + 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.migrations', |
781 | + 'djangocms_video': 'djangocms_video.migrations', |
782 | + 'django_comments': 'django_comments.migrations', |
783 | + 'menus': 'menus.migrations', |
784 | + 'rest_framework.authtoken': 'rest_framework.authtoken.migrations', |
785 | + 'reversion': 'reversion.migrations', |
786 | + 'tagging': 'tagging.migrations', |
787 | + 'taggit': 'taggit.migrations', |
788 | + 'zinnia': 'zinnia.migrations', |
789 | +} |
790 | + |
791 | +>>>>>>> MERGE-SOURCE |
792 | LOGGING = { |
793 | 'version': 1, |
794 | 'disable_existing_loggers': False, |
795 | |
796 | === modified file 'requirements.txt' |
797 | --- requirements.txt 2015-12-04 11:49:10 +0000 |
798 | +++ requirements.txt 2015-12-09 15:41:23 +0000 |
799 | @@ -1,3 +1,4 @@ |
800 | +<<<<<<< TREE |
801 | oslo.config==1.6.0 |
802 | oslo.i18n==1.2.0 |
803 | oslo.serialization==1.2.0 |
804 | @@ -8,12 +9,25 @@ |
805 | Pillow==2.6.1 |
806 | beautifulsoup4==4.4.0 |
807 | cmsplugin-zinnia==0.6 |
808 | +======= |
809 | +Django==1.8.7 |
810 | +django-template-debug==0.3.5 |
811 | +oslo.config==3.0.0 |
812 | +oslo.i18n==2.7.0 |
813 | +oslo.serialization==2.0.0 |
814 | +oslo.utils==2.8.0 |
815 | +Pillow==2.9.0 |
816 | +cmsplugin-zinnia==0.8 |
817 | +Markdown==2.6.5 |
818 | +beautifulsoup4==4.4.1 |
819 | +>>>>>>> MERGE-SOURCE |
820 | dj-database-url==0.3.0 |
821 | django-admin-enhancer==0.1.3.1 |
822 | django-appconf==0.6 |
823 | django-blog-zinnia==0.14.2 |
824 | django-ckeditor==4.4.7 |
825 | django-ckeditor-updated==4.4.4 |
826 | +<<<<<<< TREE |
827 | django-classy-tags==0.5.1 |
828 | django-cms==3.0.6 |
829 | django-contrib-comments==1.5 |
830 | @@ -25,23 +39,56 @@ |
831 | django-polymorphic==0.6 |
832 | django-reversion==1.8.4 |
833 | django-sekizai==0.7 |
834 | +======= |
835 | +django-classy-tags==0.6.2 |
836 | +django-cms==3.2.0 |
837 | +django-contrib-comments==1.6.1 |
838 | +django-meta==0.3.1 |
839 | +django-meta-mixin==0.2.1 |
840 | +django-missing==0.1.15 |
841 | +django-parler==1.5.1 |
842 | +django-polymorphic==0.7.2 |
843 | +django-reversion==1.9.3 |
844 | +django-sekizai==0.8.2 |
845 | +>>>>>>> MERGE-SOURCE |
846 | django-swiftstorage==1.1.0 |
847 | +<<<<<<< TREE |
848 | django-tagging==0.3.3 |
849 | django-taggit==0.12.2 |
850 | django-taggit-autosuggest==0.2.5 |
851 | +======= |
852 | +django-tagging==0.4 |
853 | +django-taggit==0.17.5 |
854 | +django-taggit-autosuggest==0.2.8 |
855 | +>>>>>>> MERGE-SOURCE |
856 | django-taggit-templatetags==0.2.5 |
857 | django-templatetag-sugar==1.0 |
858 | django-xmlrpc==0.1.5 |
859 | +<<<<<<< TREE |
860 | djangocms-admin-style==0.2.2 |
861 | djangocms-link==1.5 |
862 | djangocms-picture==0.1 |
863 | djangocms-snippet==1.3 |
864 | djangocms-text-ckeditor==2.4.2 |
865 | +======= |
866 | +djangocms-admin-style==1.0.7 |
867 | +djangocms-link==1.7.1 |
868 | +djangocms-picture==0.2.0 |
869 | +djangocms-snippet==1.7.1 |
870 | +djangocms-text-ckeditor==2.8.0 |
871 | +>>>>>>> MERGE-SOURCE |
872 | djangocms-utils==0.9.5 |
873 | +<<<<<<< TREE |
874 | djangocms-video==0.1 |
875 | python-keystoneclient==0.11.2 |
876 | python-swiftclient==2.3.1 |
877 | pytz==2014.7 |
878 | +======= |
879 | +djangocms-video==0.2.0 |
880 | +python-keystoneclient==1.3.3 |
881 | +python-swiftclient==2.6.0 |
882 | +pytz==2015.7 |
883 | +>>>>>>> MERGE-SOURCE |
884 | simple-translation==0.8.6 |
885 | simplejson==3.6.5 |
886 | wsgiref==0.1.2 |