diff --git a/.travis.yml b/.travis.yml index 26694aaaf580e3e50c5b4e1225f26dd5c932d8bd..74c62d2296d0fdc7dfc709fd5387f487a81c1491 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,19 @@ install: # Install project dependencies - pip install -r requirements.txt + # Install test dependencies + - pip install flake8 + + # Install node + - nvm install 8 + + # Install node dependencies + - cd ./opentech/static_src && npm install --quiet + + # Build the static files + - cd ./opentech/static_src && npm run build:prod + + # Run the tests script: # Run python code style checks diff --git a/README.md b/README.md index c7d2f35c63dc2c8bb3362e9944f79d43d558efc0..df325faa33aca6784af37b181e23253a98f74981 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,19 @@ djrun ``` This will make the site available on the host machine at: http://127.0.0.1:8000/ + +# Updating front-end files + +Any changes made to sass or js files will need to be recompiled using: + +``` bash +yarn build +``` + +Alternatively you can run the watcher that will rebuild on change to files: + +``` bash +yarn start +``` + +Both commands should be run from within the `opentech/static_src` folder in the vagrant box. diff --git a/opentech/navigation/migrations/0001_initial.py b/opentech/navigation/migrations/0001_initial.py index 260fefa7bbcf3ab1d839a55b466712ad9adc3c3a..f213cf80464613397892157f8b299f6320a4fbd8 100644 --- a/opentech/navigation/migrations/0001_initial.py +++ b/opentech/navigation/migrations/0001_initial.py @@ -23,7 +23,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('primary_navigation', wagtail.wagtailcore.fields.StreamField((('link', wagtail.wagtailcore.blocks.StructBlock((('page', wagtail.wagtailcore.blocks.PageChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(help_text="Leave blank to use the page's own title", required=False))))),), blank=True, help_text='Main site navigation')), ('secondary_navigation', wagtail.wagtailcore.fields.StreamField((('link', wagtail.wagtailcore.blocks.StructBlock((('page', wagtail.wagtailcore.blocks.PageChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(help_text="Leave blank to use the page's own title", required=False))))),), blank=True, help_text='Alternative navigation')), - ('footer_navigation', wagtail.wagtailcore.fields.StreamField((('column', wagtail.wagtailcore.blocks.StructBlock((('heading', wagtail.wagtailcore.blocks.CharBlock(blank=True, help_text='Leave blank if no header required.')), ('links', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.StructBlock((('page', wagtail.wagtailcore.blocks.PageChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(help_text="Leave blank to use the page's own title", required=False))))))))),), blank=True, help_text='Multiple columns of footer links with optional header.')), + ('footer_navigation', wagtail.wagtailcore.fields.StreamField((('column', wagtail.wagtailcore.blocks.StructBlock((('heading', wagtail.wagtailcore.blocks.CharBlock(required=False, help_text='Leave blank if no header required.')), ('links', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.StructBlock((('page', wagtail.wagtailcore.blocks.PageChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(help_text="Leave blank to use the page's own title", required=False))))))))),), blank=True, help_text='Multiple columns of footer links with optional header.')), ('footer_links', wagtail.wagtailcore.fields.StreamField((('link', wagtail.wagtailcore.blocks.StructBlock((('page', wagtail.wagtailcore.blocks.PageChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(help_text="Leave blank to use the page's own title", required=False))))),), blank=True, help_text='Single list of elements at the base of the page.')), ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), ], diff --git a/opentech/navigation/models.py b/opentech/navigation/models.py index 53d73d84a81d4077167b85fea9a9b15fa93b2202..c270b1478b29b4a670b1e6a07e2c667da83a485d 100644 --- a/opentech/navigation/models.py +++ b/opentech/navigation/models.py @@ -16,7 +16,7 @@ class LinkBlock(blocks.StructBlock): class LinkColumnWithHeader(blocks.StructBlock): - heading = blocks.CharBlock(blank=True, help_text="Leave blank if no header required.") + heading = blocks.CharBlock(required=False, help_text="Leave blank if no header required.") links = blocks.ListBlock(LinkBlock()) class Meta: diff --git a/opentech/news/migrations/0001_initial.py b/opentech/news/migrations/0001_initial.py index e8a803d18e3967ea92ed538b976e8600fdde2ea3..e9c99a7d7fc3d694c5694a61876a858673f50f38 100644 --- a/opentech/news/migrations/0001_initial.py +++ b/opentech/news/migrations/0001_initial.py @@ -55,7 +55,7 @@ class Migration(migrations.Migration): ('listing_summary', models.CharField(blank=True, help_text="The text summary used when this page appears in listings. It's also used as the description for search engines if the 'Search description' field above is not defined.", max_length=255)), ('publication_date', models.DateTimeField(blank=True, help_text='Use this field to override the date that the news item appears to have been published.', null=True)), ('introduction', models.TextField(blank=True)), - ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('citation_link', wagtail.wagtailcore.blocks.URLBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False)))))))), + ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False)))))))), ('listing_image', models.ForeignKey(blank=True, help_text='Choose the image you wish to be displayed when this page appears in listings', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), ], diff --git a/opentech/people/migrations/0001_initial.py b/opentech/people/migrations/0001_initial.py index cc6045ca54918ef5ad377f2895db81765c085450..61ae8c60b60c76bdf7652dba8741890398c2cde7 100644 --- a/opentech/people/migrations/0001_initial.py +++ b/opentech/people/migrations/0001_initial.py @@ -55,7 +55,7 @@ class Migration(migrations.Migration): ('job_title', models.CharField(max_length=255)), ('introduction', models.TextField(blank=True)), ('website', models.URLField(blank=True, max_length=255)), - ('biography', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('citation_link', wagtail.wagtailcore.blocks.URLBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False)))))), blank=True)), + ('biography', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False)))))), blank=True)), ('email', models.EmailField(blank=True, max_length=254)), ('mobile_phone', models.CharField(blank=True, max_length=255)), ('landline_phone', models.CharField(blank=True, max_length=255)), diff --git a/opentech/people/static/people/admin/js/update_person_title.js b/opentech/people/static/people/admin/js/update_person_title.js new file mode 100644 index 0000000000000000000000000000000000000000..589f89eec9ca9264439f3404a7bb5ca4e3fe246e --- /dev/null +++ b/opentech/people/static/people/admin/js/update_person_title.js @@ -0,0 +1,21 @@ +$(document).ready(function () { + var $lastNameInput = $('#id_last_name'); + var $firstNameInput = $('#id_first_name'); + var $titleInput = $('#id_title'); + var $slugInput = $('#id_slug'); + + $firstNameInput.on('input', function () {joinFirstNameLastName();}); + + $lastNameInput.on('input', function () {joinFirstNameLastName();}); + + function joinFirstNameLastName() { + var firstName = $firstNameInput.val(); + var lastName = $lastNameInput.val(); + var title = firstName + ' ' + lastName; + + $slugInput.data('previous-val', $slugInput.val()); + $titleInput.data('previous-val', $titleInput.val()); + $titleInput.val(title); + $titleInput.blur(); // Trigger slug update + } +}); diff --git a/opentech/people/wagtail_hooks.py b/opentech/people/wagtail_hooks.py index 9100b81a87ef1972677a473416ebb5e214875d8c..ceaff4b99a6bf31b8d162fda52427f98ac8b0a90 100644 --- a/opentech/people/wagtail_hooks.py +++ b/opentech/people/wagtail_hooks.py @@ -7,5 +7,5 @@ from wagtail.wagtailcore import hooks @hooks.register('insert_editor_js') def editor_js(): return mark_safe( - '<script src="%s"></script>' % static('js/update_person_title.js') + '<script src="%s"></script>' % static('people/admin/js/update_person_title.js') ) diff --git a/opentech/standardpages/migrations/0001_initial.py b/opentech/standardpages/migrations/0001_initial.py index 51278eef35460d11c6b12f0eab23dc09dc91a7bb..3e244f23e4db22de784f3a412a9da5071b082214 100644 --- a/opentech/standardpages/migrations/0001_initial.py +++ b/opentech/standardpages/migrations/0001_initial.py @@ -48,7 +48,7 @@ class Migration(migrations.Migration): ('listing_title', models.CharField(blank=True, help_text='Override the page title used when this page appears in listings', max_length=255)), ('listing_summary', models.CharField(blank=True, help_text="The text summary used when this page appears in listings. It's also used as the description for search engines if the 'Search description' field above is not defined.", max_length=255)), ('introduction', models.TextField(blank=True)), - ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('citation_link', wagtail.wagtailcore.blocks.URLBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False)))))))), + ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False)))))))), ('listing_image', models.ForeignKey(blank=True, help_text='Choose the image you wish to be displayed when this page appears in listings', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), ], diff --git a/opentech/static_src/package.json b/opentech/static_src/package.json index 44baaff128e2a57e3ed984c55b13427561676a22..0e844e9bf69b734f6d33e5ac9d595b33fa531fa7 100755 --- a/opentech/static_src/package.json +++ b/opentech/static_src/package.json @@ -69,7 +69,7 @@ "watch": "npm-run-all -p watch:*", "//[ Syncs ]//": "", - "sync:fonts": "rsync -rtvu --delete $npm_package_config_src_font/ $npm_package_config_dest_font/", + "sync:font": "rsync -rtvu --delete $npm_package_config_src_font/ $npm_package_config_dest_font/", "sync:img": "rsync -rtvu --delete $npm_package_config_src_img/ $npm_package_config_dest_img/", "sync": "npm-run-all -p sync:*", diff --git a/opentech/templates/blocks/quote_block.html b/opentech/templates/blocks/quote_block.html index 5f95799aa2432c275768ce57f06367340b67aea5..33bea5ac66717619297c0c58edbf8ab7ba740b9f 100644 --- a/opentech/templates/blocks/quote_block.html +++ b/opentech/templates/blocks/quote_block.html @@ -1,3 +1,4 @@ -<blockquote{% if value.citation_link %} cite="{{ value.citation_link }}"{% endif %}> - {{ value.quote }} +<blockquote> + <p>{{ value.quote }}</p> + {% if value.attribution %}{{ value.attribution }}{% endif %} </blockquote> diff --git a/opentech/utils/blocks.py b/opentech/utils/blocks.py index 7c97eec039f4668b3ae8ce32dbdaacd0300ea251..973b0372b3313c3e40a8fb200b7bdddcb27db742 100644 --- a/opentech/utils/blocks.py +++ b/opentech/utils/blocks.py @@ -25,7 +25,7 @@ class DocumentBlock(blocks.StructBlock): class QuoteBlock(blocks.StructBlock): quote = blocks.CharBlock(classname="title") - citation_link = blocks.URLBlock(required=False) + attribution = blocks.CharBlock(required=False) class Meta: icon = "openquote" diff --git a/vagrant/provision.sh b/vagrant/provision.sh index 77866f4b201720c13e624d15e2813e5d6635835b..4ccbdd8e3e8074fb114489a7c516abf17d00a482 100755 --- a/vagrant/provision.sh +++ b/vagrant/provision.sh @@ -50,7 +50,13 @@ alias djrunp="dj runserver_plus 0.0.0.0:8000" source $VIRTUALENV_DIR/bin/activate export PS1="[$PROJECT_NAME \W]\\$ " cd $PROJECT_DIR +EOF -alias djtestapply="dj test opentech.apply --keepdb; mypy ." +# Install node.js and npm +su - vagrant -c "curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -" +su - vagrant -c "sudo apt-get install -y nodejs" -EOF +# Build the static files +su - vagrant -c "sudo npm install -g yarn" +su - vagrant -c "cd $PROJECT_DIR/$DEST_DIR/$PROJECT_NAME/$PROJECT_NAME/static_src; yarn install" +su - vagrant -c "cd $PROJECT_DIR/$DEST_DIR/$PROJECT_NAME/$PROJECT_NAME/static_src; yarn build"