django-markdownx icon indicating copy to clipboard operation
django-markdownx copied to clipboard

override the default template django 1.11

Open davideghz opened this issue 7 years ago • 28 comments

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!

davideghz avatar Jul 07 '17 12:07 davideghz

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?

xenatisch avatar Jul 07 '17 13:07 xenatisch

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.

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

  2. 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 *

davideghz avatar Jul 07 '17 13:07 davideghz

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.

xenatisch avatar Jul 07 '17 13:07 xenatisch

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 :)

davideghz avatar Jul 07 '17 13:07 davideghz

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.

xenatisch avatar Jul 07 '17 13:07 xenatisch

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!

davideghz avatar Jul 07 '17 13:07 davideghz

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 avatar Jul 07 '17 13:07 xenatisch

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?

xenatisch avatar Jul 07 '17 14:07 xenatisch

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?!

davideghz avatar Jul 07 '17 15:07 davideghz

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?

xenatisch avatar Jul 07 '17 16:07 xenatisch

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

davideghz avatar Jul 07 '17 16:07 davideghz

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

davideghz avatar Jul 08 '17 07:07 davideghz

I'll come back to this later this evening.

xenatisch avatar Jul 08 '17 11:07 xenatisch

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

davideghz avatar Jul 09 '17 07:07 davideghz

What are dal, and dal_select2 about?

xenatisch avatar Jul 09 '17 09:07 xenatisch

They are related to Django Autocomplete Light

davideghz avatar Jul 09 '17 10:07 davideghz

@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.

xenatisch avatar Jul 09 '17 10:07 xenatisch

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

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

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

infinity1207 avatar Jul 15 '17 14:07 infinity1207

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

xenatisch avatar Jul 15 '17 15:07 xenatisch

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

suguby avatar Aug 09 '17 16:08 suguby

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

xenatisch avatar Aug 09 '17 16:08 xenatisch

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

stoneding avatar Aug 15 '17 05:08 stoneding

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'

brianbola90 avatar Mar 25 '18 10:03 brianbola90

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

sebastian-code avatar Jul 03 '18 23:07 sebastian-code

@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.

jagaudin avatar Jan 14 '21 16:01 jagaudin

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.

ericel avatar Dec 17 '22 11:12 ericel

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

stoneding avatar Dec 17 '22 11:12 stoneding