Giter Site home page Giter Site logo

dmptrluke / django-markdownfield Goto Github PK

View Code? Open in Web Editor NEW
43.0 43.0 13.0 810 KB

A simple custom field for Django that can safely render Markdown and store it in the database.

License: MIT License

Python 62.50% CSS 32.83% Jinja 4.67%
django markdown python

django-markdownfield's People

Contributors

benjaoming avatar bernd-wechner avatar carlmurray avatar dmptrluke avatar kavdev avatar kirsinger avatar kpavlovsky avatar zypro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

django-markdownfield's Issues

Issue with forms.ModelForm

I have a simple model that I only need to have this markdown field. I set up everything like was mentioned in the README, but when I go to run the migration, I constantly get this error:

File "/usr/local/lib/python3.8/site-packages/markdownfield/models.py", line 71, in formfield
if defaults['widget'] == admin_widgets.AdminTextareaWidget:
KeyError: 'widget'

It seems like the 'widget' property isn't defined on defaults and this always errors out. Any thoughts on how to fix this?

Deprecation warning from django

When running the library in django 4, the following warning is emitted:

RemovedInDjango41Warning: 'markdownfield' defines default_app_config = 'markdownfield.apps.MarkdownFieldConfig'. Django now detects this configuration automatically. You can remove default_app_config.

Admin widgets are broken when used inside inlines

Admin widgets use auto-generated JS like this (see widgets.html for source):

{{ options | json_script:options_id }}
<script nonce="{{ csp_nonce }}" type="text/javascript">
    const {{ widget.name }}_options = JSON.parse(document.getElementById('{{ options_id }}').textContent);
    const {{ widget.name }}_editor = new EasyMDE(Object.assign({
        element: document.getElementById('{{ widget.attrs.id }}'),
        hideIcons: ["side-by-side", "preview"],
        spellChecker: false,
        parsingConfig: {
            allowAtxHeaderWithoutSpace: true,
        }
    }, {{ widget.name }}_options));
</script>

It's fine for usual ModelAdmin instances, but inline admins would produce invalid code like this:

<script id="options_MZdmTr6jNmZtxZhU5tjWzw" type="application/json">{}</script>
<script nonce="" type="text/javascript">
    const tasks-0-description_options = JSON.parse(document.getElementById('options_MZdmTr6jNmZtxZhU5tjWzw').textContent);
    const tasks-0-description_editor = new EasyMDE(Object.assign({
        element: document.getElementById('id_tasks-0-description'),
        hideIcons: ["side-by-side", "preview"],
        spellChecker: false,
        parsingConfig: {
            allowAtxHeaderWithoutSpace: true,
        }
    }, tasks-0-description_options));
</script>

Obvious problem is that dash is illegal in variable names.

An obvious solution is to define the variable prefix in widget context based on self.uuid (see widgets.py) and use it instead of widget.name. As a workaround, monkey-patching markdownfield/widget.html via copying the source to any internal app and replacing {{ widget.name }}_options with {{ options_id }}_options works, although generated names like options_MZdmTr6jNmZtxZhU5tjWzw_options aren't that pretty.

P.S. I'll try to provide a MR if I have time ;)

Images not rendering

This (from documentation):

{{ post.text_rendered | safe }}

fails to render images, renders the text with a link to the image instead. Is there a renderer that will render the imagines in situ or an option to pass or another filter?

Missing install step

Hey,
The admin widget didn't work for me until I added markdownfield to my INSTALLED_APPS.
I think that should be noted in the instruction.

Feature request: do not require storing of rendered text

I do see the performance advantage of storing rendered Markdown, but also wonder why there is no documented means for avoiding it. It seems a perfectly valid and common use case to me, to store only the markdown and render on, well, rendering the template. That is rather than:

from django.db import models

from markdownfield.models import MarkdownField, RenderedMarkdownField
from markdownfield.validators import VALIDATOR_STANDARD

class Page(models.Model):
    text = MarkdownField(rendered_field='text_rendered', validator=VALIDATOR_STANDARD)
    text_rendered = RenderedMarkdownField()

and in templates:

{{ post.text_rendered | safe }}

something more like:

from django.db import models

from markdownfield.models import MarkdownField
from markdownfield.validators import VALIDATOR_STANDARD

class Page(models.Model):
    text = MarkdownField(validator=VALIDATOR_STANDARD)

and in templates:

{{ post.text | render_markdown }}

Not only is that use case to my mind very valid, I think that the render_markdown filter would find broader use in templates.

Fix minor layout issue on Django 3.2

#33 by @kavdev fixed a layout issue with django-markdownfield in newer versions of Django, but has caused a minor regression in Django 3.2.

image

This issue isn't as impactful as the issue the original code had in newer versions, so I don't want to revert the change, but I would like to find a solution to fix the regression on 3.2 while it's still supported.

Integrating internal links with relative file path in .md files

I picked up this package for my demo site and was really struggling to include internal links into markdown files, at least in my development environment. I want to be able to include relative URL file paths, i.e. /djangoapp/view/, into my .md files, rather than absolute file paths, i.e. http://localhost:3000/djangoapp/view.

My settings are divided into local.py and production.py. In my local.py, I have SITE_URL = "http://localhost:3000", but when I set a link in my .md files such as [link](/djangoapp/view/), it saved the HTML as an external link <a ref="/djangoapp/view/" target="_blank" class=" external" rel="nofollow noopener noreferrer">djangomodel</a>. If I put the absolute file path in the .md file, [link](http://localhost:3000/djangoapp/view/), the link was rendered as a local link. However, I want to use my .md files in development and production without modifying all the absolute file paths.

I first tried a solution whereby I include Django url tags ({% url "djangoapp:view" %} in my .md files and render the .md HTML equivalents as Template objects in my view, which I then served to the context. This seemed messy. Then I figured out the following one that seems pretty hacky and not that performant, but does work like I want it to.

  1. I copied a Stack Overflow answer to get all of my apps URLs.
from django.urls import URLPattern, URLResolver
def list_urls(lis, acc=None):
    #https://stackoverflow.com/questions/1275486/how-can-i-list-urlpatterns-endpoints-on-django#answer-54531546
    if acc is None:
        acc = []
    if not lis:
        return
    l = lis[0]
    if isinstance(l, URLPattern):
        yield acc + [str(l.pattern)]
    elif isinstance(l, URLResolver):
        yield from list_urls(l.url_patterns, acc + [str(l.pattern)])
    yield from list_urls(lis[1:], acc)
  1. I overwrote the django-markdownfield format_link function and modified it to check all links registered as external for membership in a list of internal links.
def format_link(attrs: Dict[tuple, str], new: bool = False):
    """
    This is really weird and ugly, but that's how bleach linkify filters work.
    """
    try:
        p = urlparse(attrs[(None, 'href')])
    except KeyError:
        # no href, probably an anchor
        return attrs

    if not any([p.scheme, p.netloc, p.path]) and p.fragment:
        # the link isn't going anywhere, probably a fragment link
        return attrs

    if hasattr(settings, 'SITE_URL'):
        c = urlparse(settings.SITE_URL)
        link_is_external = p.netloc != c.netloc
    else:
        # Assume true for safety
        link_is_external = True

    if link_is_external:
        # create a list of all the app's URLs and check if the hyperlink path is in that list
        urlconf = __import__(settings.ROOT_URLCONF, {}, {}, [''])
        app_urls = ["/" + ''.join(url_part_list) for url_part_list in list_urls(urlconf.urlpatterns)]
        if p.path not in app_urls:
            # link is external - secure and mark
            attrs[(None, 'target')] = '_blank'
            attrs[(None, 'class')] = attrs.get((None, 'class'), '') + ' external'
            attrs[(None, 'rel')] = 'nofollow noopener noreferrer'

    return attrs
  1. I overwrote the django-markdownfield MarkdownField to substitute the new format_link function.
class OverwrittenMarkdownField(MarkdownField):
    def pre_save(self, model_instance, add):
        value = super().pre_save(model_instance, add)

        if not self.rendered_field:
            return value

        dirty = markdown(
            text=value,
            extensions=EXTENSIONS,
            extension_configs=EXTENSION_CONFIGS
        )

        if self.validator.sanitize:
            if self.validator.linkify:
                cleaner = bleach.Cleaner(tags=self.validator.allowed_tags,
                                         attributes=self.validator.allowed_attrs,
                                         css_sanitizer=self.validator.css_sanitizer,
                                         filters=[partial(LinkifyFilter,
                                                          callbacks=[format_link, blacklist_link])])
            else:
                cleaner = bleach.Cleaner(tags=self.validator.allowed_tags,
                                         attributes=self.validator.allowed_attrs,
                                         css_sanitizer=self.validator.css_sanitizer)

            clean = cleaner.clean(dirty)
            setattr(model_instance, self.rendered_field, clean)
        else:
            # danger!
            setattr(model_instance, self.rendered_field, dirty)

        return value
  1. Use the OverwrittenMarkdownField in my model field definitions, as opposed to the MarkdownField natively provided by the package.

This results in the desired behavior whereby relative internal links are saved and rendered with the HTML for an internal link (no target="_blank").

Appreciate any feedback.

Don't assume django-csp is being used

Since this project has been more widely-adopted, the assumption that django-csp is being used should be removed from widget.html.

Support for CSP_NONCE should be maintained, but the nonce attribute should not be added to the HTML if CSP_NONCE is not defined in the template.

Release 0.11

I've been doing some PR merges. I'd like to run a manual test before releasing 0.11. But the changes have all looked non-breaking.

CC: @dmptrluke

Proposal: preview mode

Would be great to preview. Since there is rendering included already, it doesn't sound hard to implement the preview feature. Seems to miss just a place(modal, whatever else) where this preview might be. WDYT? @benjaoming @dmptrluke @kpavlovsky

Otherwise thanks for the tool!

Ability to add images with SEO in-mind? Ability to add more buttons? Ex ( code )

I'm in a markdown editor research nightmare. I've installed and uninstalled many markdown and WYSIWYG editors at this point.

My needs are:

  1. The ability to upload images to server
  2. Maintain best SEO practices for those images
  3. Add markdown
  4. Add buttons to the toolbar

Does django-markdownfield give access to the EasyMDE object to modify the toolbar for example?

No module named markdownfield.fields

Hi there,

It seems the documentation is somewhat out of date. It refers to the module markdownfields.fields which doesn't actually exist.

Greetings,
JJ

How to set markdown

I've already written readme files, I just want to store source code of markdown files, is there a way we can do this?

Documentation needs to be clearer - very simple step missing

As a beginner it was definitely not obvious that I had to add "markdownfield" to my INSTALLED_APPS. I spent almost an hour banging my head against a wall until I finally decided to look for a different package - only when looking at docs for other packages did I realise that maybe I had to add it to my INSTALLED APPS.

A single sentence on the package documentation page would save a lot of headache. Please add this step to the docs. It's not obvious for beginners.

Document how to use MDEWidget on a form

Title says it all. Just isn't obvious, and I'm having to work it out as best possible. Be nice to have a little doc on it, or a mention in the README. Basically my forms are rendering with a standard TextArea it seems and I'm thinking I need to do something to get the MDEWidget in its place.

Multiline code doesn't work

Hello!

First of all thanks for this awesome library.

I am really grateful for this work!

Now the problem:

Multiline code block with three backticks is represented as P tag with CODE tag inside.

<p>
    <code>print(f"Hello, {username}")</code>
</p>

This leads to ignored formatting, newlines, etc

Is it possible to make code blocks being wrapped in PRE tag?

<pre>
    <code>alert();</code>
</pre>

Thank you, have a great day!

Is this maintained?

Hi,

I want to know if this is maintained. As i plan to use this project in a production build of mine i would gladly be willing to help modernize this project

Unable to add links: 'Settings' object has no attribute 'SITE_URL'

Hello! I am running into an issue where this plugin is throwing an error when I try to post a new Model Object with a markdownfield. Specifically, it seems it's having issues when trying to parse URLs. It happens when I try adding the URL manually or using the editor's function. When the links are removed, everything POSTs just fine. Am I missing some install or configuration step?

Here is the traceback from Django:

Request Method: POST
Request URL: http://localhost:8000/admin/blog/blog/add/

Django Version: 4.0.4
Python Version: 3.9.12
Installed Applications:
['fontawesomefree',
 'markdownfield',
 'blog.apps.BlogConfig',
 'projects.apps.ProjectsConfig',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "/home/bryson/.local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/contrib/admin/options.py", line 683, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/utils/decorators.py", line 133, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/views/decorators/cache.py", line 62, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/contrib/admin/sites.py", line 242, in inner
    return view(request, *args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/contrib/admin/options.py", line 1885, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/utils/decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/utils/decorators.py", line 133, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/contrib/admin/options.py", line 1745, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/contrib/admin/options.py", line 1797, in _changeform_view
    self.save_model(request, new_object, form, not add)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/contrib/admin/options.py", line 1220, in save_model
    obj.save()
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/base.py", line 806, in save
    self.save_base(
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/base.py", line 857, in save_base
    updated = self._save_table(
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/base.py", line 1000, in _save_table
    results = self._do_insert(
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/base.py", line 1041, in _do_insert
    return manager._insert(
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/query.py", line 1434, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1620, in execute_sql
    for sql, params in self.as_sql():
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1547, in as_sql
    value_rows = [
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1548, in <listcomp>
    [
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1549, in <listcomp>
    self.prepare_value(field, self.pre_save_val(field, obj))
  File "/home/bryson/.local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1497, in pre_save_val
    return field.pre_save(obj, add=True)
  File "/home/bryson/.local/lib/python3.9/site-packages/markdownfield/models.py", line 98, in pre_save
    clean = cleaner.clean(dirty)
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/sanitizer.py", line 184, in clean
    return self.serializer.render(filtered)
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/_vendor/html5lib/serializer.py", line 398, in render
    return "".join(list(self.serialize(treewalker)))
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/html5lib_shim.py", line 705, in serialize
    for stoken in super().serialize(treewalker, encoding):
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/_vendor/html5lib/serializer.py", line 265, in serialize
    for token in treewalker:
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/linkifier.py", line 520, in __iter__
    yield from self.handle_a_tag(token_buffer)
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/linkifier.py", line 481, in handle_a_tag
    attrs = self.apply_callbacks(attrs, False)
  File "/home/bryson/.local/lib/python3.9/site-packages/bleach/linkifier.py", line 254, in apply_callbacks
    attrs = cb(attrs, is_new)
  File "/home/bryson/.local/lib/python3.9/site-packages/markdownfield/util.py", line 34, in format_link
    c = urlparse(settings.SITE_URL)
  File "/home/bryson/.local/lib/python3.9/site-packages/django/conf/__init__.py", line 88, in __getattr__
    val = getattr(self._wrapped, name)

Exception Type: AttributeError at /admin/blog/blog/add/
Exception Value: 'Settings' object has no attribute 'SITE_URL'

The model in concern:

class Project(models.Model):
  title = models.CharField(max_length=200)
  current = models.BooleanField(default=True)
  overview = models.TextField()
  description = MarkdownField(rendered_field='text_rendered',use_editor=False, use_admin_editor=True, validator=VALIDATOR_STANDARD)
  text_rendered = RenderedMarkdownField()
  image = models.ImageField(upload_to='projects/static/projects/images/')

Let me know if you need more information.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.