neutronX / django-markdownx

Comprehensive Markdown plugin built for Django
https://neutronx.github.io/django-markdownx/
Other
863 stars 153 forks source link

override the default template django 1.11 #76

Open davideghz opened 7 years ago

davideghz commented 7 years ago

Hi, I followed the docs and I created a new BASE_DIR/templates/markdownx/widget2.html file with the following:

<div class="markdownx row">
    <div class="col-md-6">
        {{ markdownx_editor }}
    </div>
    <div class="col-md-6">
        <div class="markdownx-preview"></div>
    </div>
</div>

Nothing happen when I visit my page, I don't see the new class added.

The form is defined in forms.py as follow:

class ForumThreadForm(forms.Form):
    content = MarkdownxFormField()

and in the views.py I have:

def forum_new_thread(request):
    form = ForumThreadForm()
    context = {'form': form}
    return render(request, 'forum/new_thread.html', context)

My settings.py is:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'frontend.custom_context.track_seen_today',
                'frontend.custom_context.track_seen_always',
                'frontend.custom_context.user_avatar',
            ],
        },
    },
]

What am I doing wrong? Thanks!

xenatisch commented 7 years ago

Hi, and thanks for your question.

First things first:

  1. what version of Django you are using?
  2. is markdownx added to the INSTALLED_APPS in your settings file?
  3. your context_processors need to include these two processors as well (they should be there by default, unless you haven't auto-generated your settings.py file):
    • django.template.context_processors.media
    • django.template.context_processors.static
  4. when you say that's your settings.py, I take it you don't mean that it is the whole thing, right?
davideghz commented 7 years ago

Hi! Thanks for your prompt answer: 1) I'm on 1.11 2) yes it added to INSTALLED_APPS: is there a specific position I have to place it? I currently have:

INSTALLED_APPS = [
    'dal',
    'dal_select2',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'markdownx',
    'frontend',
]

being frontend my main app.

3) I didn't have those 2 context_processors, but event after addition of them and server restart, nothing happen.

4) right :) the whole thing is as follow:

"""
Django settings for mysoundlist project.

Generated by 'django-admin startproject' using Django 1.11.2.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os
from django.utils.translation import ugettext_lazy as _

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '...'

ALLOWED_HOSTS = ['xyz.herokuapp.com', 'localhost', '127.0.0.1']

# Application definition

INSTALLED_APPS = [
    'dal',
    'dal_select2',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'markdownx',
    'frontend',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'frontend.custom_context.track_seen_today',
                'frontend.custom_context.track_seen_always',
                'frontend.custom_context.user_avatar',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
            ],
        },
    },
]
ROOT_URLCONF = 'xyz.urls'

WSGI_APPLICATION = 'xyz.wsgi.application'

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'CET'

USE_I18N = True

USE_L10N = True

USE_TZ = True

LANGUAGES = [
  ('it', _('Italian')),
  ('en', _('English')),
]

LOCALE_PATHS = [
    os.path.join(BASE_DIR, 'locale')
]

# REGISTRATION STUFF
LOGIN_REDIRECT_URL = '/profile/'

# MEDIA STUFF
MEDIA_URL = 'images/'
MEDIA_ROOT = os.path.join(BASE_DIR, "")

# FORUM STUFF
MARKDOWNX_EDITOR_RESIZABLE = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles')
STATIC_URL = '/static/'

# Extra places for collectstatic to find static files.
STATICFILES_DIRS = (
    os.path.join(PROJECT_ROOT, 'static'),
)

# Simplified static file serving.
# https://warehouse.python.org/project/whitenoise/

STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'

# Loading test/prod settings based on ENV settings
ENV = os.environ.get('ENV')

if ENV == 'prod':
    try:
        from .production_settings import *
    except ImportError:
        pass
else:
    from .local_settings import *
xenatisch commented 7 years ago

Thank heavens you didn't include your secret_key and database password. It's the first thing I check every time people post the entire thing.

You seem to be doing everything right as far as I can see; and no, the order of inclusion in INSTALLED_APPS is not important.

So, before I go into the details of how you're trying to render stuff, would you mind just renaming widget2.html file to widget.html? The reason I'm asking you to do this is that I think there was an issue a while back with that we addressed (don't remember the exact details), and I'm not sure if we have updated the PyPi repository afterwards.

If that didn't resolve the issue, just renamed it back to widget2.html, and let's have a look at your views.py, models.py, and urls.py (unless you're using it in your admins, in which case, please include that one too), or relevant sections thereof.

davideghz commented 7 years ago

I tried to rename widget2 in widget.html w/ any luck. I copy and paste here below the relevant sections of the mentioned files:

views.py

def forum_new_thread(request):
    form = ForumThreadForm()
    context = {'form': form}
    return render(request, 'forum/new_thread.html', context)

models.py

class ForumThread(models.Model):
    dj = models.ForeignKey(DjProfile)
    user = models.ForeignKey(User, blank=True, null=True)
    title = models.CharField(max_length=200)
    content = MarkdownxField()
    creation_date = models.DateField(auto_now_add=True)
    edit_date = models.DateField(auto_now=True)

    def __str__(self):
        return self.title

urls.py

urlpatterns += i18n_patterns(
    ...
    url(r'^markdownx/', include('markdownx.urls')),
    url(r'^new-thread/$', views.forum_new_thread, name='forum_new_thread'),
    prefix_default_language=False
) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

thanks a lot for your interest in my issue :)

xenatisch commented 7 years ago

Ok, this is getting interesting!

And your form looks like this?

from django.forms import Form

from markdownx.fields import MarkdownxFormField

class ForumThreadForm(Form):
    # ...
    content = MarkdownxFormField()

Also, does it work without the customised widget?

If not, perhaps run ./manage.py makemigrations, ./manage.py migrate and see if it works?

and you're very welcome. We try our best depending on our time.

davideghz commented 7 years ago

slighty different but I think it should be the same:

from markdownx.fields import MarkdownxFormField
from django import forms

class ForumThreadForm(forms.Form):
    content = MarkdownxFormField()

makemigations and migrate return No changes detected and No migrations to apply. (I already run them)

without the customized widget (I mean, without the widget2.html file) everything works, nothing change!

xenatisch commented 7 years ago

Ok, I'm going somewhere and will be back to work on this again in about an hour (just so you're not sitting there waiting until then).

xenatisch commented 7 years ago

Ok... I read this thing all over again.

When you say you don't see any new classes added, what do you exactly mean? I mean, do the editor / preview elements render in your webpage or nothing renders all together? Do you see any errors displayed by Django? Could you check the browser console to see if there are any JavaScript errors?

If they do render, what is it exactly that you are expecting to see? Maybe give an example?

davideghz commented 7 years ago

I get the following html:

<form method="POST" action="">
    <input type="hidden" name="csrfmiddlewaretoken" value="theToken">
    <label for="id_content">Content:</label>
    <div class="markdownx">
        <textarea name="content" cols="40" rows="10" required="" id="id_content" class="markdownx-editor" data-markdownx-editor-resizable="" data-markdownx-urls-path="/markdownx/markdownify/" data-markdownx-upload-urls-path="/markdownx/upload/" data-markdownx-latency="500" style="transition: opacity 1s ease;"></textarea>

        <div class="markdownx-preview"></div>
    </div>
</form>

The textarea is correctly rendered and and I don't get any error in Django or JS console, but I would expect to see the col-md-6 css classe somewhere in the html.

Am I missing something?!

xenatisch commented 7 years ago

Oh... now I see what you mean.

I can now say for certain that the issue is not caused by the JavaScript (I reviewed that section of the code this morning). There is probably something we're both missing here.

The thing is, the template must be served and rendered automatically by Django when placed in the correct location (it has nothing to do with MarkdownX). You are placing the file in the correct location, but it is still not rendered. All your settings, as far as I can see, appear to be correct too.

So the only thing left here is that Django cannot locate the file for some reason.

We can really force Django to find the file through a small hack, but that wouldn't tell us anything about the reason why the file is not served properly.

Here is how:

forms.py

from django import forms

from markdownx.widgets import MarkdownxWidget

# ----------------------------------------------------------------------------

class CustomMarkdownxWidget(MarkdownxWidget):
    template_name = "path_to_your_custom_template"  # <====

class CustomMarkdownxFormField(forms.CharField):
    def __init__(self, *args, **kwargs):
        super(MarkdownxFormField, self).__init__(*args, **kwargs)

        if issubclass(self.widget.__class__, forms.widgets.MultiWidget):
            is_markdownx_widget = any(
                issubclass(item.__class__, CustomMarkdownxWidget)
                for item in getattr(self.widget, 'widgets', list())
            )

            if not is_markdownx_widget:
                self.widget = CustomMarkdownxWidget()

        elif not issubclass(self.widget.__class__, CustomMarkdownxWidget):
            self.widget = CustomMarkdownxWidget()

# ----------------------------------------------------------------------------

class ForumThreadForm(forms.Form):
    content = CustomMarkdownxFormField()

Ok, the hack aside (and if it doesn't work, I would seriously think there is an external issue); do you think there might be an app (or middleware) that causes some sort of behavioural change in Django when it comes to serving / rendering templates?

davideghz commented 7 years ago

Many thanks for your answer; now i'm travelling, i can check and followup tomorrow morning!

davideghz commented 7 years ago

Even the small hack does not solve my issue, I really think it's about Django not locating the template. I share with you my tree, maybe you notice something I'm not doing right

.
├── db.sqlite3
├── frontend
│   ├── admin.py
│   ├── apps.py
│   ├── custom_context.py
│   ├── forms.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── ...
...
│   ├── models.py
│   ├── static
│   │   ├── custom.css
│   │   ├── custom.js
│   │   ├── empty.txt
│   │   └── images
│   │       ├── background.jpg
│   │       ├── bckg.png
│   │       ├── dav.jpg
│   │       ├── hopk.jpg
│   │       ├── icons
│   │       │   ├── icon_beatport.png
│   │       │   ├── icon_facebook.png
│   │       │   ├── icon_residentadvisor.png
│   │       │   ├── icon_soundcloud.png
│   │       │   └── icon_youtube.png
│   │       ├── john.jpg
│   │       ├── msl-logo-bianco.png
│   │       └── nobody.jpg
│   ├── templatetags
│   │   ├── helper_tags.py
│   │   ├── __init__.py
│   ├── tests.py
│   └── views.py
├── locale
│   └── it
│       └── LC_MESSAGES
│           ├── django.mo
│           └── django.po
├── manage.py
├── mysoundlist
│   ├── __init__.py
│   ├── local_settings.py
│   ├── production_settings.py
│   ├── settings.py
│   ├── static
│   │   └── empty.txt
│   ├── urls.py
│   └── wsgi.py
├── Procfile
├── README.md
├── requirements.txt
├── runtime.txt
├── templates
│   ├── base.html
│   ├── dj_profiles
│   │   ├── partials
│   │   │   ├── _breadcrumbs.html
│   │   │   ├── _events.html
│   │   │   ├── _producers.html
│   │   │   ├── _threads.html
│   │   │   └── _tracks.html
│   │   ├── show_events.html
│   │   ├── show_events_track.html
│   │   ├── show.html
│   │   ├── show_producers.html
│   │   ├── show_threads.html
│   │   └── show_tracks.html
│   ├── forum
│   │   └── new_thread.html
│   ├── home.html
│   ├── layout
│   │   ├── footer.html
│   │   └── header.html
│   ├── markdownx
│   │   └── widget2.html
│   ├── registration
│   │   ├── login.html
│   │   ├── password_reset_complete.html
│   │   ├── password_reset_confirm.html
│   │   ├── password_reset_done.html
│   │   ├── password_reset_email.html
│   │   ├── password_reset_form.html
│   │   ├── password_reset_subject.txt
│   │   └── signup.html
│   └── user_profiles
│       └── show.html
└── uploads
    └── dj_images
xenatisch commented 7 years ago

I'll come back to this later this evening.

davideghz commented 7 years ago

I ended up doing the whole thing in CSS, but if I could override the field template everything would have been easier! So the issue is still pending

xenatisch commented 7 years ago

What are dal, and dal_select2 about?

davideghz commented 7 years ago

They are related to Django Autocomplete Light

xenatisch commented 7 years ago

@davideghz could you try that hack thing with a different filename? Like, put that widget2.html in a different directory and rename it to something else all together, then use the new path in the hack I gave you. Let's see if that works.

If that doesn't work either, then when need to find out why is Django failing to serve the templates. Even so it would fall outside the boundaries of MarkdownX, I would still like to find out what this is happening.

infinity1207 commented 7 years ago

@davideghz I solved it, please add something to your settings.py

INSTALLED_APPS = [ .... 'django.forms', .... ]

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

xenatisch commented 7 years ago

@infinity1207 care to elaborate? Why + are there any citations as to why this has happened?

infinity1207 commented 7 years ago

@xenatisch https://stackoverflow.com/questions/42048977/how-to-add-css-class-to-widget-field-with-django-1-11-template-based-form-render

suguby commented 7 years ago

I have the some problem, this topic help me, tnx!

But I'm using Jinja2 renderer...

And I came across a problem: in template context no markdownx_editor variable :( I looked in the code markdownx/widgets.py

if not DJANGO_VERSION[:2] < (1, 11):
    return super(MarkdownxWidget, self).render(name, value, attrs, renderer)

No any markdownx_editor passed to render... I am surprised :) This was tested with Jinja2?

Me settings here https://pastebin.com/Rbv4feSR

(intopython_2_env) wad@wad-thinkpad:~/PycharmProjects/intopython (develop)$ pip freeze
Django==1.11.4
django-jinja==2.3.1
django-markdownx==2.0.21
Jinja2==2.9.6
Markdown==2.6.8
xenatisch commented 7 years ago

@suguby I'm afraid not, this has not been tested on Jinja.

stoneding commented 7 years ago

i think you may not add django-bootstrap3 when you installed it add tab{% bootstrap_css %} and{% bootstrap_javascript %} in your widget2.html.

brianbola90 commented 6 years ago

I found the work around for this if interested. It will depend how you have setup templates though.

TEMPLATES = [ {

See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND

    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
    'DIRS': [
        str(APPS_DIR.path('templates/markdownx')),
        str(APPS_DIR.path('templates')),
    ],
    'OPTIONS': {
        # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
        'debug': DEBUG,
        # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
        # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
        'loaders': [
            'django.template.loaders.filesystem.Loader',
            'django.template.loaders.app_directories.Loader',
        ],
        # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
        'context_processors': [
            'django.template.context_processors.debug',
            'django.template.context_processors.request',
            'django.contrib.auth.context_processors.auth',
            'django.template.context_processors.i18n',
            'django.template.context_processors.media',
            'django.template.context_processors.static',
            'django.template.context_processors.tz',
            'django.contrib.messages.context_processors.messages',
            # Your stuff: custom template context processors go here
        ],
    },
},

] add 'django.forms', to installed apps.

Add FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

sebastian-code commented 6 years ago

I struggled with this for a while and the solution provided by @infinity1207 was the one which helped me. Thanks by the way.

jagaudin commented 3 years ago

@xenatisch @infinity1207 There is a mention of the django.forms apps being required in the documentation

TemplatesSetting

class TemplatesSetting This renderer gives you complete control of how widget templates are sourced. It uses get_template() to find widget templates based on what’s configured in the TEMPLATES setting.

Using this renderer along with the built-in widget templates requires either:

  • 'django.forms' in INSTALLED_APPS and at least one engine with APP_DIRS=True.

  • Adding the built-in widgets templates directory in DIRS of one of your template engines. [...]

Using this renderer requires you to make sure the form templates your project needs can be located.

ericel commented 1 year ago

This has to do with the way the markdownx widget calls the template. Currently it is set to be: template_name = 'markdownx/widget.html'

However, I think changing that to: template_name = 'widgets/markdownx/widget.html'

Should reader the template correctly. If I am right, then the only option you have is to use @xenatisch patch. If there should be an update in the future of the package code to correctly call the widgets templates, then this shouldn’t be a problem.

stoneding commented 1 year ago

【本信息为自动回复】您发给我的信件已经收到。--世东