📜 ⬆️ ⬇️

GUI installer for Django 1.6.2 for Windows

0. Introduction


Good day, Habr!
It all started with the fact that I needed to make a GUI installer for Django under Windows . Without seeing any article describing the creation of installers, I thought, "Why not?" From the Django community on this issue, I was answered approximately as the habrauzer ffriend did . Thanks to him for being constructive and open. Details under the cut.

Here is a letter I received from the Django community in response to my fi . As a matter of fact, it was precisely because of this fi that I turned out to be in the minus in terms of reputation with a rating.
Text
Hi Ivan,

Thanks for the suggestion.
')
The contact form you've used is for the DSF - we're the fund raising
arm Django project.

If you’d like it, you should direct
a ticket on the django-developers mailing list
Django's ticket tracker.

Better still
see, and provide those changes to the Django codebase.
Windows or Cyrillic
users, we can integrate those changes into the core product.

Yours,
Russ Magee% -)
I must say how not to do it. Do not try to use the limited edition of InstallShield, attached bonus to MSVS Pro 2013.
Perhaps, in some simple cases, it fits more than, but in my case as much control as possible over the process was desirable, and when I opened InstallShield and began to disassemble it by bone, the latter disappointed me with its limitations.
Fortunately, in this world there is NSIS - NullSoft Install System, namely - a script generator of installers with eye-pleasing features:
View the list
  • ZLIB, BZIP2 and LZMA support (files can be compressed together or separately).
  • Generation of uninstall programs.
  • Customizable user interface: dialogs, fonts, backgrounds, icons, text, checkmarks, images, etc.
  • Classic and modern wizard interfaces.
  • Multi-language support in one installation. Over 60 translations are available, but you can also create your own. Unicode support allows even more languages ​​to be used.
  • Per-page organization logic: you can add standard wizard pages or create your own.
  • Tree to select installation components.
  • Ability to define configurations: usually minimal, typical and complete installations, and it is also possible to create a custom configuration.
  • CRC32 self check
  • Low cost of compression, namely - 34 Kb with default parameters.
  • The ability to display the license agreement RTF or TXT.
  • Ability to identify the destination directory from the registry.
  • Many plug-ins for creating custom dialogs, Internet connections, HTTP downloads, file modification, WinAPI calls, etc.
  • Installers can be as much as 2 GB.
  • Additional silent mode for automated installations.
  • Preprocessor with support for certain characters, macros, conditional compilation, standard predefined.
  • Coding with PHP and include elements (include user variables, stack, real flow control.


Installers have their own virtual machines that allow the following

  • Extract files with custom rewrite options.
  • Copy files and directories, rename, delete, search.
  • Plugin to call DLL.
  • Registration DLL, ActiveX, deregistration.
  • Execution shell with the possibility of waiting for the execution of operations.
  • Creating a shortcut.
  • Work with the registry.
  • Reading and writing ini.
  • Reading and writing a common text file.
  • Flexible manipulations with integer and string values.
  • Opening a window based on the name of the class or its name.
  • User interface manipulation: font, text installation.
  • Posting window.
  • User interaction with message windows or pages.
  • Branching, comparison and stuff in this spirit.
  • The ability to control the situation in the event of errors.
  • Reboot, including delete or rename on reboot.
  • Commands behavior installer. For example, "show", "hide", "expect".
  • User functions in the script.
  • Callback functions for user actions.
This list is taken from relatively complete documentation for sourceforge , whereas the standard package contains only general information about the generator and its language, which is stated in the corresponding help file.
To my surprise, sf did not have a link to the archive with this most comprehensive documentation. I corrected this mistake by myself .
At the same time, there is an archive with a set of “simple guides” for the implementation of specific features, which previously had to be downloaded via third-party file hosting by a third-party program. Fixed this bug .
There are a great many - sorry for the pun - the GUI generator generator generators ( HM NIS Edit turned out to be the best), but at the same time there is only one method of productive language teaching ...

1. So ...


The simplest code that does not require special explanation, for the first acquaintance with the general principles:
Become acquainted
  1. # define installer name
  2. OutFile "installer.exe"
  3. # set desktop as install directory
  4. InstallDir $ DESKTOP
  5. # default section start
  6. Section
  7. # define output path
  8. SetOutPath $ INSTDIR
  9. # specify file to go in output path
  10. File test.txt
  11. # define uninstaller name
  12. WriteUninstaller $ INSTDIR \ uninstaller.exe
  13. # -------
  14. # default section end
  15. SectionEnd
  16. # create a section to define what the uninstaller does.
  17. # the section will always be named "Uninstall"
  18. Section "Uninstall"
  19. # Always delete uninstaller first
  20. Delete $ INSTDIR \ uninstaller.exe
  21. # now delete installed file
  22. Delete $ INSTDIR \ test.txt
  23. SectionEnd
Yes, I'm lazy, I used copy-paste from here , adapting it to fit my needs.
By the way, the UFO had no nsi highlighting, so highlight.hohli.com was used, perhaps this information would be useful.
I do not understand the intricacies of python, but judging by the setup.py file in Django-1.6.2 when installing, the work goes only and only with the paths in this or that system.
Django setup.py code
import os import sys from distutils.core import setup from distutils.sysconfig import get_python_lib # Warn if we are installing over top of an existing installation. This can # cause issues where files that were deleted from a more recent Django are # still present in site-packages. See #18115. overlay_warning = False if "install" in sys.argv: lib_paths = [get_python_lib()] if lib_paths[0].startswith("/usr/lib/"): # We have to try also with an explicit prefix of /usr/local in order to # catch Debian's custom user site-packages directory. lib_paths.append(get_python_lib(prefix="/usr/local")) for lib_path in lib_paths: existing_path = os.path.abspath(os.path.join(lib_path, "django")) if os.path.exists(existing_path): # We note the need for the warning here, but present it after the # command is run, so it's more likely to be seen. overlay_warning = True break def fullsplit(path, result=None): """ Split a pathname into components (the opposite of os.path.join) in a platform-neutral way. """ if result is None: result = [] head, tail = os.path.split(path) if head == '': return [tail] + result if head == path: return result return fullsplit(head, [tail] + result) EXCLUDE_FROM_PACKAGES = ['django.conf.project_template', 'django.conf.app_template', 'django.bin'] def is_package(package_name): for pkg in EXCLUDE_FROM_PACKAGES: if package_name.startswith(pkg): return False return True # Compile the list of packages available, because distutils doesn't have # an easy way to do this. packages, package_data = [], {} root_dir = os.path.dirname(__file__) if root_dir != '': os.chdir(root_dir) django_dir = 'django' for dirpath, dirnames, filenames in os.walk(django_dir): # Ignore PEP 3147 cache dirs and those whose names start with '.' dirnames[:] = [d for d in dirnames if not d.startswith('.') and d != '__pycache__'] parts = fullsplit(dirpath) package_name = '.'.join(parts) if '__init__.py' in filenames and is_package(package_name): packages.append(package_name) elif filenames: relative_path = [] while '.'.join(parts) not in packages: relative_path.append(parts.pop()) relative_path.reverse() path = os.path.join(*relative_path) package_files = package_data.setdefault('.'.join(parts), []) package_files.extend([os.path.join(path, f) for f in filenames]) # Dynamically calculate the version based on django.VERSION. version = __import__('django').get_version() setup( name='Django', version=version, url='http://www.djangoproject.com/', author='Django Software Foundation', author_email='foundation@djangoproject.com', description=('A high-level Python Web framework that encourages ' 'rapid development and clean, pragmatic design.'), license='BSD', packages=packages, package_data=package_data, scripts=['django/bin/django-admin.py'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: WSGI', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', ], ) if overlay_warning: sys.stderr.write(""" ======== WARNING! ======== You have just installed Django over top of an existing installation, without removing it first. Because of this, your install may now include extraneous files from a previous version that have since been removed from Django. This is known to cause a variety of problems. You should manually remove the %(existing_path)s directory and re-install Django. """ % {"existing_path": existing_path}) 
I don’t understand python, but in general it’s clear that work with paths depending on the axis is going on, checking whether the installation is on top of the existing Django and setup () seems to contain information for the python package as a whole .
In addition, the INSTALL file states:
AS AN ALTERNATIVE, django directory to Python's
site-packages directory, which is located wherever your python installation
lives. Some places you might check are:

/usr/lib/python2.7/site-packages (Unix, Python 2.7)
/usr/lib/python2.6/site-packages (Unix, Python 2.6)
C: \\ PYTHON \ site-packages (Windows)

However, it was precisely because I did not know the python that I chose a more kosher setup.py, because I do not know for sure what the absence of setup () would say. That's what I got after adapting bigtest.nsi. The comments highlighted places that are not intuitive, which had to tinker until everything went as it should.
Full installer script code
  1. ! include "Include \ LogicLib.nsh"
  2. OutFile "installer.exe"
  3. Name "Django 1.6.2"
  4. Caption "Django 1.6.2"
  5. Icon "$ {NSISDIR} \ Contrib \ Graphics \ Icons \ nsis1-install.ico"
  6. CRCCheck on
  7. SilentInstall normal
  8. BGGradient 000000 0000FF FFFFFF
  9. InstallColors FF8080 000030
  10. XPStyle on
  11. InstallDir $ EXEDIR / * Provides a default installation to where the installer was saved * /
  12. SetFont "Tahoma" 10 / * I like this font and that's it * /
  13. SetOverWrite on
  14. # -NonSectionInstructionsEnd-
  15. Section "BaseInstructions"
  16. SetDatablockOptimize on
  17. / * Without setting the output path for the files, the installer swore
  18. the impossibility of recording already at run time,
  19. although it seems that InstallDir is enough
  20. SetOutPath "$ INSTDIR \ *"
  21. SetOverWrite on
  22. File / r "C: \ ins \ Django-1.6.2 \ *" / * That folder in which I had Django.
  23. This is how you can include the necessary files in the installer * /
  24. SectionEnd
  25. RequestExecutionLevel highest / * Out of harm's way ... * /
  26. CheckBitmap "$ {NSISDIR} \ Contrib \ Graphics \ Checks \ classic-cross.bmp"
  27. LicenseText "Django license"
  28. LicenseData "license.rtf" / * Got off ... laziness =) * /
  29. / * "How many languages ​​do you know, so many times you are a man" =)
  30. NSIDIR, as you can see, is the generator installation folder.
  31. The whole language itself is remarkable intuitive * /
  32. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ English.nlf"
  33. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ Dutch.nlf"
  34. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ French.nlf"
  35. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ German.nlf"
  36. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ Korean.nlf"
  37. LoadLanguageFile "Russian.nlf" / * Slightly corrected translation file into native * /
  38. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ Spanish.nlf"
  39. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ Swedish.nlf"
  40. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ TradChinese.nlf"
  41. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ SimpChinese.nlf"
  42. LoadLanguageFile "$ {NSISDIR} \ Contrib \ Language files \ Slovak.nlf"
  43. ; Set name using the normal interface (Name command)
  44. LangString Name $ { LANG_ENGLISH } "English"
  45. LangString Name $ { LANG_DUTCH } "Dutch"
  46. LangString Name $ { LANG_FRENCH } "French"
  47. LangString Name $ { LANG_GERMAN } "German"
  48. LangString Name $ { LANG_KOREAN } "Korean"
  49. LangString Name $ { LANG_RUSSIAN } "Russian"
  50. LangString Name $ { LANG_SPANISH } "Spanish"
  51. LangString Name $ { LANG_SWEDISH } "Swedish"
  52. LangString Name $ { LANG_TRADCHINESE } "Traditional Chinese"
  53. LangString Name $ { LANG_SIMPCHINESE } "Simplified Chinese"
  54. LangString Name $ { LANG_SLOVAK } "Slovak"
  55. ; Directly change the inner lang strings (Same as ComponentText)
  56. LangString ^ ComponentsText $ { LANG_ENGLISH } "English component page"
  57. LangString ^ ComponentsText $ { LANG_DUTCH } "Dutch component page"
  58. LangString ^ ComponentsText $ { LANG_FRENCH } "French component page"
  59. LangString ^ ComponentsText $ { LANG_GERMAN } "German component page"
  60. LangString ^ ComponentsText $ { LANG_KOREAN } "Korean component page"
  61. LangString ^ ComponentsText $ { LANG_RUSSIAN } "Russian component page"
  62. LangString ^ ComponentsText $ { LANG_SPANISH } "Spanish component page"
  63. LangString ^ ComponentsText $ { LANG_SWEDISH } "Swedish component page"
  64. LangString ^ ComponentsText $ { LANG_TRADCHINESE } "Traditional Chinese component page"
  65. LangString ^ ComponentsText $ { LANG_SIMPCHINESE } "Simplified Chinese component page"
  66. LangString ^ ComponentsText $ { LANG_SLOVAK } "Slovak component page"
  67. ; A LangString for the section name
  68. LangString Sec1Name $ { LANG_ENGLISH } "English section # 1"
  69. LangString Sec1Name $ { LANG_DUTCH } "Dutch section # 1"
  70. LangString Sec1Name $ { LANG_FRENCH } "French section # 1"
  71. LangString Sec1Name $ { LANG_GERMAN } "German section # 1"
  72. LangString Sec1Name $ { LANG_KOREAN } "Korean section # 1"
  73. LangString Sec1Name $ { LANG_RUSSIAN } "Russian section # 1"
  74. LangString Sec1Name $ { LANG_SPANISH } "Spanish section # 1"
  75. LangString Sec1Name $ { LANG_SWEDISH } "Swedish section # 1"
  76. LangString Sec1Name $ { LANG_TRADCHINESE } "Trandional Chinese section # 1"
  77. LangString Sec1Name $ { LANG_SIMPCHINESE } "Simplified Chinese section # 1"
  78. LangString Sec1Name $ { LANG_SLOVAK } "Slovak section # 1"
  79. ; --------------------------------
  80. Function .onInit
  81. ; Language selection dialog
  82. Push ""
  83. Push $ { LANG_ENGLISH }
  84. Push English
  85. Push $ { LANG_DUTCH }
  86. Push dutch
  87. Push $ { LANG_FRENCH }
  88. Push french
  89. Push $ { LANG_GERMAN }
  90. Push german
  91. Push $ { LANG_KOREAN }
  92. Push korean
  93. Push $ { LANG_RUSSIAN }
  94. Push russian
  95. Push $ { LANG_SPANISH }
  96. Push spanish
  97. Push $ { LANG_SWEDISH }
  98. Push swedish
  99. Push $ { LANG_TRADCHINESE }
  100. Push "Traditional Chinese"
  101. Push $ { LANG_SIMPCHINESE }
  102. Push "Simplified Chinese"
  103. Push $ { LANG_SLOVAK }
  104. Push Slovak
  105. Push A ; A means auto count languages
  106. ; push button (push "") must remain
  107. LangDLL :: LangDialog "Installer Language" "Please select the language of the installer"
  108. Pop $ LANGUAGE
  109. StrCmp $ LANGUAGE "cancel" 0 + 2
  110. Abort
  111. FunctionEnd
  112. ; --------------------------------
  113. ; --------------------------------
  114. Page license
  115. Page directory
  116. Page instfiles
  117. AutoCloseWindow false
  118. ShowInstDetails show
  119. Function "FinalStage"
  120. MessageBox MB_OK "Close window and see on your screen ... Thanks!"
  121. FunctionEnd
  122. / * Pay attention to the wrapper. It would seem that should
  123. to work without a section, just a call and we finished it, but no ... :( * /
  124. Section "Final"
  125. Call "FinalStage"
  126. SectionEnd
  127. Function .onGUIEnd / * There are other callbacks * /
  128. ClearErrors
  129. / * With a clean install, you could do without
  130. by copying django to site-packages, however using setup.py
  131. It seemed to me more kosher, considering the meta-information. I don't know python
  132. so I don’t know where and how the absence of the setup () call comes around. * /
  133. FileOpen $ 0 $ INSTDIR \ installer.bat w
  134. IfErrors done
  135. FileWrite $ 0 "cd $ INSTDIR $ \ npython setup.py install" / * I don’t always like Abracadabra with registers * /
  136. / * It should be noted here that adding something like del / Q $ INSTDIR \ installer.bat to a batch file does not lead to good.
  137. I would be grateful if they tell me why this is happening, because it is not nuggly and it seems intuitively
  138. that if the command is launched, the presence or absence of the file with the command does not play a role, but not the same ...
  139. FileClose $ 0
  140. done:
  141. Exec "$ INSTDIR \ installer.bat"
  142. FunctionEnd
  143. / * It would be possible to make a page with the choice of the python folder, in case the python is not registered in the PATH -
  144. that would be perfect, but I'm not a perfectionist maniac and I’ll do it only if the installer
  145. Someone will like it and I will be indicated on the defect. * /
And below - the screen of one of the results when running around the generator.

It remains only to reach out to the Django community again and make trivialities such as specifying the version, file name, etc ...
I hope that this post has appeared to someone useful and rather informative.

Source: https://habr.com/ru/post/219285/


All Articles